Some metaprogramming utility stuff:
template<class T>
struct tag_t {using type=T;};
template<class T>
constexpr tag_t<T> tag{};
template<auto I>
using constant_t = std::integral_constant<std::decay_t<decltype(I)>, I>;
template<auto I>
constexpr constant_t<I> constant{};
template<class T, T...ts>
struct sequence{};
We need sequence
because std::integer_sequence
is overspecified.
Now we have our enum:
enum class SomeEnum {
A,B,C,
ValueCount,
};
Some enum helpers:
template<class E, std::size_t...Is>
constexpr auto EnumsSequence( std::index_sequence<Is...> ) {
return sequence<E, static_cast<E>(Is)...>{};
}
template<class E> // requires E::ValueCount
constexpr std::size_t EnumsSize(tag_t<E> =tag<E>) {
return static_cast<std::size_t>(E::ValueCount);
}
template<class E> // requires E::ValueCount
constexpr auto EnumsSequence(tag_t<E> =tag<E>) {
return EnumsSequence<E>(std::make_index_sequence< EnumsSize(tag<E>) >{} );
}
template<class E>
using make_enums_sequence = decltype( EnumsSequence(tag<E>) );
we can now do make_enums_sequence<SomeEnum>
and get std::integral_sequence<SomeEnum, SomeEnum::A, SomeEnum::B, SomeEnum::C>
. Woo, really basic compile-time reflection. It works on any type with a ValueCount
enumerator, or you can override EnumsSize(tag_t<E>)
and/or EnumsSequence(tag_t<E>)
for a specific type if you have a different convention.
Next we want to make a variant from an integral_sequence
:
template<class S>
struct variant_over_helper;
template<class E, E...es>
struct variant_over_helper< sequence<E, es...> > {
using type=std::variant< constant_t<es>... >;
};
template<class E>
using variant_over_t = typename variant_over_helper<make_enums_sequence<E>>::type;
let us think about the variant variant_over_t<SomeEnum>
.
It contains an index of what alternative it holds. Alternative number 0 corresponds to SomeEnum::A
, whose value is 0. Alternative number 1 corresponds to SomeEnum::B
.
Basically, variant_over_t<SomeEnum>
a struct containing an integer that lines up with SomeEnum
's value. Except we can std::visit
this variant. Bwahahah.
Next we need to be able to convert a runtime SomeEnum
to a variant_over_t<SomeEnum>
.
template<class E, E... es>
variant_over_t<E> get_variant_enum( sequence<E, es...>, E e ) {
using generator = variant_over_t<E>(*)();
const generator gen[] = {
[]()->variant_over_t<E> {
return constant<es>;
}...
};
return gen[ static_cast<std::size_t>(e) ]();
}
template<class E>
variant_over_t<E> get_variant_enum( E e ) {
return get_variant_enum( EnumsSequence(tag<E>), e );
}
and we are done.
struct Base{
virtual ~Base() {}
};
enum class SomeEnum
{
First,
Second,
Third,
ValueCount
};
template<SomeEnum i>
struct Foo:Base{};
void bar()
{
std::unique_ptr<Base> ptr;
SomeEnum fooType = static_cast<SomeEnum>( rand() % (int)SomeEnum::ValueCount );
auto vFooType = get_variant_enum(fooType);
ptr = std::visit( [](auto efoo)->std::unique_ptr<Base>{
return std::make_unique<Foo<efoo>>();
}, vFooType );
}
Live example.
Note that this requires that your enum be contiguous and start at 0 (for the gen[]
array lookup). Some work could be done to handle non-contiguous cases, but that gets complex, so no don't do it. (use EnumsSequence
to either build a if-tree to find what index to use with std::in_place_index_t<I>
or constant<E>
, and/or build an enum-to-index mapping.)
This code runs in O(1) time, not counting data that can be initialized once.
I used ValueCount
as the number of elements in the enum. If you really need it to be called Last
, simply add:
constexpr std::size_t EnumsSize( tag_t<decltype(SomeEnum::Last)> ) {
return static_cast<std::size_t>(SomeEnum::Last);
}
to the namespace of tag_t
or the namespace of SomeEnum
, and all of the code automatically adapts via argument dependent lookup.
Best part? All of that metaprogramming to create get_variant_enum
complies down to:
movzx edx, ah
mov WORD PTR [rsp+14], ax
ie, copy some bytes over.
The visit call is a bit more complex.