admin管理员组

文章数量:1405629

I have run into a strange challenge trying to define a type based on an array while writing some library binding code.

Consider an enumeration like the following:

enum class MyTypes
{
    UInt,
    Int,
    Float
}

With some type traits, I can convert a value of the enumeration into a type:

template <MyTypes Ty> struct typeify;
template <> struct typeify<MyTypes::UInt> { using type = unsigned int; };
template <> struct typeify<MyTypes::Int> { using type = int; };
template <> struct typeify<MyTypes::Float> { using type = float; };
template <MyTypes Ty> using typeify_t = typename typeify<Ty>::type;

To help with the binding, I also have this (because C++ doesn't have reflection):

inline static constexpr std::array KnownTypes 
{ 
    std::pair{"UInt", MyTypes::UInt},
    std::pair{"Int", MyTypes::Int},
    std::pair{"Float", MyTypes::Float}
};

From here, I want to define a std::variant based on my known types.

With 3 values, the obvious thing is to just write the variant like so:

std::variant<unsigned int, int, float>

But, in my situation, I have 50+ types. So, I would like a way to automatically generate the type definition for the std::variant from the array of KnownTypes and the typeify_t type trait.

I think I have gotten close with the following, but I have been stymied by not being able to give a variadic template parameter a default value:

template <template <typename T, T ... Idx> class temp = std::make_index_sequence<KnownTypes.size()>>
using AnyKnownType = std::variant<typeify_t<KnownTypes[Idx].second>, ...>;

I have run into a strange challenge trying to define a type based on an array while writing some library binding code.

Consider an enumeration like the following:

enum class MyTypes
{
    UInt,
    Int,
    Float
}

With some type traits, I can convert a value of the enumeration into a type:

template <MyTypes Ty> struct typeify;
template <> struct typeify<MyTypes::UInt> { using type = unsigned int; };
template <> struct typeify<MyTypes::Int> { using type = int; };
template <> struct typeify<MyTypes::Float> { using type = float; };
template <MyTypes Ty> using typeify_t = typename typeify<Ty>::type;

To help with the binding, I also have this (because C++ doesn't have reflection):

inline static constexpr std::array KnownTypes 
{ 
    std::pair{"UInt", MyTypes::UInt},
    std::pair{"Int", MyTypes::Int},
    std::pair{"Float", MyTypes::Float}
};

From here, I want to define a std::variant based on my known types.

With 3 values, the obvious thing is to just write the variant like so:

std::variant<unsigned int, int, float>

But, in my situation, I have 50+ types. So, I would like a way to automatically generate the type definition for the std::variant from the array of KnownTypes and the typeify_t type trait.

I think I have gotten close with the following, but I have been stymied by not being able to give a variadic template parameter a default value:

template <template <typename T, T ... Idx> class temp = std::make_index_sequence<KnownTypes.size()>>
using AnyKnownType = std::variant<typeify_t<KnownTypes[Idx].second>, ...>;
Share Improve this question edited Mar 8 at 12:08 JeJo 33.5k7 gold badges55 silver badges95 bronze badges asked Mar 7 at 23:59 DBS4261DBS4261 4624 silver badges9 bronze badges 1
  • You have a type mismatch, whereas temp is a template, std::make_index_sequence<KnownTypes.size()> is a class. – Jarod42 Commented Mar 8 at 14:42
Add a comment  | 

3 Answers 3

Reset to default 4

Any problem in programming can be solved with another level of indirection (except too many levels of indirection).

Instead of directly creating a using expression template, create a function template declaration and use its return type in your using expression:

template <std::size_t... Is>
std::variant<typeify_t<KnownTypes[Is].second>...>
make_variant_type(std::index_sequence<Is...>);

using AnyKnownType = decltype(make_variant_type(std::make_index_sequence<KnownTypes.size()>()));

Demo

Using partially specializing I propose:

#include <variant>
#include <cstddef>

// Helper template to generate a variant type from KnownTypes.
template <typename IndexSeq> struct variant_maker_impl;

template <std::size_t... Is> struct variant_maker_impl<std::index_sequence<Is...>>
{
    using type = std::variant< typeify_t<KnownTypes[Is].second>... >;
};

// Alias for the generated variant type.
using AnyKnownType = typename variant_maker_impl<std::make_index_sequence<KnownTypes.size()>>::type;

As far as I know, this is the option that you could easily come up with. Due to C++’s rules on alias templates, you cannot directly partially specialize an alias template. Alias templates don’t allow partial specialization, which means you can’t write an alias that “unwraps” an index sequence without using a helper structure. Therefore, converting from above to your approach is not possible.

((See live demo))

Actually, you defined 3 different sequences for your framework:

  • the enum

  • the list of template specializations

  • the array linking an enum value to a string

You could at least reduce to 2 sequences as follows:

  1. an array holding the enum values

  2. the list of template specializations. Since they are related, it is better to put there both the type and the associated string

Then you can build your variant following the possibility to transform the content of a constexpr array into a parameter pack (see a general method here), which leads to:

#include <array>
#include <variant>

#define VALUES UInt, Int, Float
enum  MyTypes {VALUES};
constexpr std::array MyTypesArray = {VALUES};

template <MyTypes Ty> struct typeify;
template <> struct typeify<MyTypes::UInt>  { constexpr static const char* name="UInt";  using type = unsigned int; };
template <> struct typeify<MyTypes::Int>   { constexpr static const char* name="Int";   using type = int;          };
template <> struct typeify<MyTypes::Float> { constexpr static const char* name="Float"; using type = float;        };

////////////////////////////////////////////////////////////////////////////////
// see https://stackoverflow/questions/60434033/how-do-i-expand-a-compile-time-stdarray-into-a-parameter-pack
template <auto arr, template <auto...> typename Consumer, typename IS = decltype(std::make_index_sequence<arr.size()>())>
struct Generator;

template <auto arr, template <auto...> typename Consumer, std::size_t... I>
struct Generator<arr, Consumer, std::index_sequence<I...>> 
{
    using type = Consumer<arr[I]...>;
};

////////////////////////////////////////////////////////////////////////////////
template <auto...s> 
struct ToVariantConsumer  { using type = std::variant <typename typeify<s>::type...>;  };

using AnyKnownType = typename Generator<MyTypesArray, ToVariantConsumer>::type::type;

static_assert (std::is_same_v<AnyKnownType, std::variant<unsigned int, int, float> >);

////////////////////////////////////////////////////////////////////////////////
int main() {}

Note the not-so-pretty #define used for avoid redundancy for both defining the enum and defining an array holding all the enum values (this is another story and solutions seem to exist).

Note also that it will run starting with c++20 (i.e. usage of non-type template parameter of class type in the Generator definition).

本文标签: cCreate a variant from an array whose elements are transformed into typesStack Overflow