【问题标题】:Build an enum using variadic template parameters使用可变参数模板参数构建枚举
【发布时间】:2013-09-25 04:45:15
【问题描述】:

我有一个相当简单的变体类,它支持一组预定义的类型,并提供一个枚举来指示哪些可用类型当前处于活动状态。像这样的:

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

我想将类制作成模板,其中支持的类型作为模板参数提供:

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

我依赖于捕获错误的一个关键特性是我可以在活动类型上switch,如果任何可能的情况被遗漏,编译器会发出警告。使用上述设计(也不使用boost::variant)是不可能的。

我的问题是,有没有什么方法可以自动生成一个枚举数与参数包中的参数数相同的枚举?

枚举的实际名称/值无关紧要,因为它们可以隐藏在用于将类型映射到正确枚举的 constexpr 函数后面。我可以想象这样的最终用法:

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

【问题讨论】:

  • 不得不switch 依赖于变量类型几乎是从来没有一个好的设计。继承和虚函数通常是合适的替代品。
  • 这似乎是the XY problem 的一个示例,您有一个不起作用的解决方案,需要帮助。但是对于您尝试解决的实际问题,可能有不止一种解决方案。描述您想要完成的事情(连同您尝试过的事情)通常会产生更好的答案。
  • @JoachimPileborg 当然,几乎永远不会。但在必要的情况下,最好有一个通用的变体模板来减轻痛苦。我不是在尝试用变体解决问题,而是在尝试编写一个健壮的类,以最大程度地减少变体是正确解决方案时的痛苦。
  • @Troy 感谢特洛伊。不幸的是,boost::any 遇到了与我的第二个版本相同的问题。它只给你一个std::type_info,这意味着要进行级联的if/else比较而不是switch——所以编译器仍然无法诊断遗漏的案例。

标签: c++ c++11


【解决方案1】:

这个问题已经存在两年了,但由于没有答案,而且我在其他地方也没有找到解决方案,所以这是我解决问题的一种方法。

第一个明显的问题是枚举类型的参数数量匹配(让编译器进行开关检查)。我看到的唯一方法是一系列模板专业化。

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
    };
};

是的,它需要一些手动输入,但这不是一个大问题,请记住,类型的数量通常是有限的,并且可以根据需要添加新的专业化(即该方法是静态安全的)。 Tag 参数用于区分具有相同数量值的枚举。

其次我们需要一些递归模板魔法来枚举类型:

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);
};

最后一个将所有部分组合在一起的类:

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;
        }
};

瞧!你可以切换! (用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;
}

功能总结:

  • 静态拒绝不在枚举中的类型
  • 静态警告缺少开关盒
  • 包中不同标签和/或参数的枚举类型不兼容

功能缺失:

  • 需要一些模板魔法来拒绝重复的参数包

【讨论】:

  • 哇。这几乎与我 2 年前在没有人回答我的问题时自己提出的解决方案完全相同!我唯一没有做的就是在枚举模板中使用标签类型,以确保它不能与错误的变体一起使用——我必须去添加它。
【解决方案2】:

在可变参数模板中获取类型的索引并将其用作枚举值。这样,您就不需要为不同数量的类型复制所有代码。这不处理列表中的重复类型,但索引将是可变参数列表中的第一个出现。如果需要重复检测,可以使用std::is_same 添加它,并以与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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-04-21
    • 2012-02-25
    • 1970-01-01
    • 1970-01-01
    • 2021-04-04
    • 1970-01-01
    相关资源
    最近更新 更多