// I was investigating a variant-like typesafe union like so:
// (Support code at the end of the message).
class MyUnionType {
public:
enum class TypeEnum { A_TYPE, B_TYPE, C_TYPE, };
template <typename T>
MyUnionType(T t) : tag_(type_enum<T>) {}
operator TypeEnum() const { return tag_; }
private:
TypeEnum tag_;
};
class A { };
class B { };
class C { };
ASSIGN_ENUM_TAG(MyUnionType::TypeEnum, A, A_TYPE);
ASSIGN_ENUM_TAG(MyUnionType::TypeEnum, B, B_TYPE);
ASSIGN_ENUM_TAG(MyUnionType::TypeEnum, C, C_TYPE);
void Example() {
MyUnionType value(B{});
switch (value) {
case type_enum<A>:
printf("A\n");
break;
case type_enum<B>:
printf("B\n");
break;
case type_enum<C>:
printf("C\n");
break;
}
}
but then I thought it would be nice to be able to do this same trick with std::variant.One could imagine this would work like so with std::variant:void Example(const std::variant<A, B, C>& v) {
switch(get_type_enum(v)) {
case type_enum<A>:
...
break;
case type_enum<B>:
...
break;
// Ideally compilers would warn here -Wswitch that the third variant parameter isn't handled. Worst case this would read VARIANT_TYPE_3.
// Using integers, this would not warn.
}
}
I got pretty far with this:
I had a template method that would return a unique enum type from a std::varint<Ts...>, but I ran into the following problems:
- If it switch takes a type T (implicitly convertible to an integral type), it tries to convert the arguments to the integral type rather than the argument type T (which is implicitly convertible).
- And also on the other side, an inability to extract the associated Ts... from the uniquely constructed enum:
template <typename... Ts>
class A { enum Type {} };
Cannot deduce A from Type.
I feel like solving this for variant would only require passing just a wee bit more template information through the switch statement by way of:
allowing:
template <typename T> enum class {};
or,
Modifying the conversion rules in switch like so:
If the switch statement implicitly converts from T to type EnumT, then, if the case statements are implicitly convertible to T, constexpr convert them to T, and then constexpr convert the T to EnumT, otherwise convert directly to EnumT if it can. This way the template information can be passed through this intermediary struct.
or,
Allow purely inner definitions to be deducible. (Deduce T for A<T>::B where template <typename T> class A { class B {}; }; (This may have a edge-case with specializations).
or,
Type traits struct like coroutines for switch of T? This is a bit more of a stretch, and probably isn't worth it, but would allow switch(std::variant...) (as would the first one)
// Support code for the example at the beginning of the message:
// Defaults to something that isn't an enum.
template <typename Enum, typename T, typename = std::enable_if_t<std::is_enum<Enum>::value>>
struct converted_to_enum {};
template <typename T>
class TypeEnumHelper {
public:
template <typename Enum, Enum = converted_to_enum<Enum, T>::value>
constexpr operator Enum() const {
return converted_to_enum<Enum, T>::value;
}
};
template <typename T>
constexpr TypeEnumHelper<T> type_enum = TypeEnumHelper<T>();
#define ASSIGN_ENUM_TAG(UnionEnumType, Class, EnumTag) \
template<> \
struct converted_to_enum<UnionEnumType, Class> { \
static constexpr UnionEnumType value = UnionEnumType:: EnumTag; \
};