1

I have a fairly simple variant class which supports a predefined set of types, and provides an enumerate to indicate which of the available types is currently active. Something like this:

class variant
{ 
  enum class type { integer, real, string, etc };
  type active_type() const;
  /* ... */ 
};

I would like to make the class into a template where the supported types are supplied as template parameters:

template <typename... T>
class variant
{ 
  const std::type_info& active_type() const; // logical, but can't switch on it
  /* ... */ 
};

A key feature that I rely on for catching errors is that I can switch on the active type and the compiler will warn if any of the possible cases have been missed. This is not possible using the above design (nor using boost::variant).

My question is, is there any way for me to automatically generate an enum with the same number of enumerates as the number of arguments in the parameter pack?

The actual names/values of the enumerates would not matter, as they can be hidden behind constexpr functions used to map the type to the correct enumerate. I could imagine an eventual usage like this:

template <typename... T>
class variant
{
  enum class type { T... }; // magic here

  // specializations provided to map T into type (for use in case labels)
  template <typename T>
  static constexpr type type_enum();

  type active_type() const;
  /* ... */
};

typedef variant<int, float, std::string> myvar;
myvar var;
switch (var.active_type())
{
case myvar::type_enum<int>(): // static constexpr function
  ...
  break;
case myvar::type_enum<float>():
  ...
  break;
} // warning: enumeration for string not handled in switch
marack
  • 2,024
  • 22
  • 31
  • 1
    Having to `switch` depending on a variables type is almost *never* a good design. Inheritance and virtual functions are often a suitable replacement. – Some programmer dude Sep 25 '13 at 04:49
  • 3
    It seems this is an example of [the XY problem](http://meta.stackexchange.com/questions/66377/what-is-the-xy-problem), you have a solution that doesn't work, and want help with it. But there may be more than one solution to your actual problem that you try to solve. Describing what you want to accomplish (together with what you have tried) often yields better answers. – Some programmer dude Sep 25 '13 at 04:56
  • The source of http://www.boost.org/doc/libs/1_51_0/boost/spirit/home/support/detail/hold_any.hpp might give you some ideas. – goji Sep 25 '13 at 05:03
  • @JoachimPileborg Sure, _almost_ never. But on those occasions where it is necessary, it's nice to have a generalized variant template to ease the pain. I'm not trying to solve a problem with a variant, I'm trying to write a robust class that minimizes the pain for when a variant is the right solution. – marack Sep 25 '13 at 05:18
  • @Troy Thanks for that Troy. Unfortunately `boost::any` suffers from the same problem as my second version from the question. It only gives you a `std::type_info`, which means going to a cascade of `if/else` comparisons instead of a `switch` - so missed cases still can't be diagnosed by the compiler. – marack Sep 25 '13 at 05:21
  • Yes it's possible, but only with a macro, not with a variadic template. – n. m. could be an AI Sep 25 '13 at 08:02

2 Answers2

5

This question is two years old, but since there are no answers and I found no solutions elsewhere, here's a way how I have solved the problem.

First obvious problem is enum type with matching number of parameters (to let compiler do switch checking). The only way I see is a series of template specializations.

template <typename Tag, size_t size> struct Enum;
template <typename Tag> struct Enum<Tag, 0> {
    enum class Type { };
};
template <typename Tag> struct Enum<Tag, 1> {
    enum class Type {
        VALUE0
    };
};
template <typename Tag> struct Enum<Tag, 2> {
    enum class Type {
        VALUE0, VALUE1
    };
};
template <typename Tag> struct Enum<Tag, 3> {
    enum class Type {
        VALUE0, VALUE1, VALUE2
    };
};
template <typename Tag> struct Enum<Tag, 4> {
    enum class Type {
        VALUE0, VALUE1, VALUE2, VALUE3
    };
};

Yes, it requires some manual input, but it's not a big problem, keeping in mind that number of types is usually limited and new specializations can be added as they are required (i.e. the approach is statically safe). The Tag parameter is used to distinguish enums with same number of values.

Secondly we need some recursive template magic to enumerate types:

template <typename EnumType, typename Type, typename... Types> struct TypeInfo;
template <typename EnumType, typename Type, typename... Types> struct TypeInfo<EnumType, Type, Type, Types...> {
    static const EnumType value = EnumType::VALUE0;
};
template <typename EnumType, typename Type, typename FirstType, typename... Types> struct TypeInfo<EnumType, Type, FirstType, Types...> {
    static const EnumType value = static_cast<EnumType>(static_cast<typename std::underlying_type<EnumType>::type>(TypeInfo<EnumType, Type, Types...>::value) + 1);
};

Finally a class to bring all pieces together:

template <typename Tag, typename... Ts> class TypedEnum {
    private:
        struct InternalTag;

    public:
        static const size_t NUM_TYPES = sizeof...(Ts);
        typedef typename Enum<InternalTag, NUM_TYPES>::Type Type;

        template <typename T> static constexpr decltype(TypeInfo<Type, T, Ts...>::value) getValue() { // SFINAE guard
            return TypeInfo<Type, T, Ts...>::value;
        }
};

Voila! You can switch! (Tested with clang)

struct Tag0;
struct Tag1;
typedef TypedEnum<Tag0, int, float, char> Enum0;
typedef TypedEnum<Tag0, bool, float, char> Enum1; // Incompatible with Enum0!

Enum0::Type tpy = Enum0::getValue<char>(); // 2
switch(tpy) {
    case Enum0::getValue<int>():
        break;
    case Enum0::getValue<float>():
        break;
    case Enum0::getValue<char>():
        break;
}

Summary of features:

  • Statically rejecting types that are not in enum
  • Statically warn about missing switch cases
  • Incompatible enum types for different tags and/or parameters in pack

Features missing:

  • Needs some template magic to reject parameter packs with duplicates
Dmitry
  • 1,230
  • 10
  • 19
  • Wow. This is almost the exact same solution I came up with myself 2 years ago when no one answered my question! The only thing I didn't do the same was to use a tag type with the enum template to ensure it can't be used with the wrong variant - I'll have to go add that in. – marack Sep 28 '15 at 21:47
2

Get the index of the type in a variadic template and use that as the enum value. This way, you don't need to copy pasta all the code for different numbers of types. This doesn't handle duplicate types in the list, but the index will be the first occurrence in the variadic list. If there needs to be duplicate detection it can be added using std::is_same and looping over all the types for each type in a similar fashion to IndexOf.

    #include <type_traits>
    using std::size_t;

    template <size_t Idx, class Q, class... Ts>
    struct IndexOfImpl;

    // base case
    template <size_t Idx, class Q>
    struct IndexOfImpl<Idx, Q>{};

    template <size_t Idx, class Q, class T>
    struct IndexOfImpl<Idx, Q, T>
        : public std::conditional_t<
            std::is_same<Q, T>::value, // Is Query the same as T?
                                        // if so
            std::integral_constant<size_t, Idx>, // inheret from Idx
            void> // type not found
    {
    };

    template <size_t Idx, class Q, class T, class... Ts>
    struct IndexOfImpl<Idx, Q, T, Ts...>
        : public std::conditional_t<
            std::is_same<Q, T>::value,  // Is Query the same as T?
                                        // if so
            std::integral_constant<size_t, Idx>, // inheret from Idx
            IndexOfImpl<Idx + 1, Q, Ts...>> // else try the trailing types
    {
    };

    // Initial case at Idx 0
    template <class Q, class... Ts>
    struct IndexOf : public IndexOfImpl<0, Q, Ts...>
    {
    };

    // Base case
    template <class Q>
    struct IndexOf<Q>
    {
    };


    // Store the type list in Ts...
    template <class... Ts>
    struct TypeEnum {
        // Get index/enum value of type T
        template <class T>
        static constexpr decltype(IndexOf<T, Ts...>::value) getValue() {
            return IndexOf<T, Ts...>::value;
        }
    };

    template <>
    struct TypeEnum<> {};

    struct Unknown;

    int main() {
        using Tags = TypeEnum<Unknown, int, float, long long, unsigned int, double>;

        size_t tag = Tags::getValue<double>();

        switch(tag) {
            case Tags::getValue<int>(): return 0;
            case Tags::getValue<float>(): return 0;
            case Tags::getValue<long long>(): return 0;
            case Tags::getValue<unsigned int>(): return 0;
            case Tags::getValue<double>(): return 0;
            default: return 1;
        }
    }

On compiler explorer