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
|
3 Answers
Reset to default 4Any 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:
an array holding the enum values
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).
发布者:admin,转转请注明出处:http://www.yc00.com/questions/1744906825a4600309.html
temp
is a template,std::make_index_sequence<KnownTypes.size()>
is a class. – Jarod42 Commented Mar 8 at 14:42