【问题标题】:Combine enums c++结合枚举c ++
【发布时间】:2013-08-20 20:45:53
【问题描述】:

在我的项目中,我有几个类似的枚举声明;

enum Comparison
{
    LT,     // "<"
    GT,     // ">"
    EQ,     // "=="
    LTEQ,   // "<="
    GTEQ,   // ">="
    NEQ     // "!="
};
enum Arithmetic
{
    ADD,    // "+"
    SUB,    // "-"
    MUL,    // "*"
    DIV,    // "/"
    MOD,    // "%"
};

我想将其中几个组合成一个组合枚举,这样;

  • 所有元素(来自子枚举)都存在于组合枚举中。
  • 所有元素都有一个唯一值(显然)。
  • 所有元素在组合枚举中的值与原始值一致。

像这样:

enum Comparison
{
    LT,     // "<"
    GT,     // ">"
    EQ,     // "=="
    LTEQ,   // "<="
    GTEQ,   // ">="
    NEQ     // "!="

    ADD,    // "+"
    SUB,    // "-"
    MUL,    // "*"
    DIV,    // "/"
    MOD,    // "%"
};

另外,我希望能够做的是将组合枚举“转换”为原始枚举之一,仅给定组合枚举中的值(假设值是一致的,这应该是微不足道的)。

枚举的替代方案是基于类的解决方案,其中类实现operator int() 运算符。

注意;我确实相信operator int() 在某种程度上是可行的方法。

【问题讨论】:

  • 当两个元素在不同的enums 中具有相同的值时会发生什么?
  • 我没有明确地为任何元素赋值,我希望编译器在可能的情况下自动处理它。我当前的“修复”是简单地将每个枚举中的第一个元素分配给前一个枚举中最后一个元素的值(+1)。 - 但是我一点也不喜欢。
  • 你为什么希望有几个枚举以及一个“主”枚举?
  • 因为枚举代表了几个逻辑实体,例如;比较运算符、关键字、分隔符等。 - 但是对于词法分析器,我需要这些实体的每个元素的唯一 ID。
  • @Skeen:如果您没有明确设置值,编译器并没有真正为您设置它。默认是从 0 开始并为每个新的枚举元素递增。明确设置它们可以为您提供所需的控制。

标签: c++ c++11 enums


【解决方案1】:

我经常看到的是这样的:

enum OperationType {
    Comparison = 0x100,
    Arithmetic = 0x200
};        

enum ComparisonType
{
    LT = Comparison,     // "<"
    GT,     // ">"
    EQ,     // "=="
    LTEQ,   // "<="
    GTEQ,   // ">="
    NEQ     // "!="
};
enum ArithmeticType
{
    ADD = Arithmetic,    // "+"
    SUB,    // "-"
    MUL,    // "*"
    DIV,    // "/"
    MOD,    // "%"
};

与简单的链接相比,这为您提供了更多的灵活性,因为现在您可以在不中断算术的情况下添加比较,并且算术和比较不需要相互了解。获取枚举的类型也变得微不足道:

constexpr OperationType getOperationType(unsigned value)
{return static_cast<OperationType>(value&0xFF00);}

【讨论】:

  • 有什么方法可以获取枚举元素的数量(在编译时)?
  • @Skeen:用这种方法,是的,但不容易。您需要为它们中的每一个添加一个“Last###Element”枚举,然后对它们进行总结。您可以编写一个函数来做到这一点,这样任何查看代码的人都不会挠头并诅咒您。但是,如果这是您所追求的功能,那么您确实需要一个大型的单一枚举。
  • @Skeen:目的是确保每个类别/类型都有足够的空间。即使在单个枚举中,也无法防止重复。
  • @Skeen:我能想到的最好的方法是:coliru.stacked-crooked.com/…,我发现它方式比链式枚举更丑陋、更危险。不利的一面是,这些值不再是编译时常量,因此它们不能再在 switch 中使用。
  • @GrantRostig Fixed
【解决方案2】:

enum 链接在一起(例如,如果子类需要扩展唯一集)的一种常见(但不是特别优雅)的方法是让每个enum 提供一个“last”值并使用它来启动下一个:

enum Comparison
{
    LT,     // "<"
    ...
    NEQ,    // "!="
    LastComparison
};

enum Logical
{
    AND = LastComparison,
    OR,
    ...
    LastLogical
};

【讨论】:

  • 这是我现在正在使用的,我正在寻找更优雅的东西;)
【解决方案3】:

不幸的是,枚举不是为组合而设计的,所以 - 除非实现一些基于工厂的 ID 生成器,但这从枚举和编译时解决方案中得出 - 你不能做更多 Ben Jackson 或 @987654322 建议的事情@。

还要考虑 - 从语言的角度来看 - 枚举不需要是连续的,所以没有办法知道它们中有多少是一个枚举(而且知道它也没有什么意义,因为它们的值可以是任何东西),因此编译器无法提供任何自动机制来链接(杰克逊)或分叉(鸭子),因此只能由您来组织它们。上述循环解决方案都是有效的,除非您处于无法定义自己的枚举值的位置(例如,因为您从其他人的 API 获得它们)。

在最后一种情况下,唯一的可能性是重新定义自己的组合(使用其他值)并通过转换函数映射到原始值。

【讨论】:

  • 我可以更改枚举。此外,如果不要求枚举是连续的,那么在使用任何一种建议的方法之前,我真的不应该按顺序设置子枚举的所有值吗?
  • 是的,每个没有值声明的枚举(枚举中的常量)都有前一个加一。您只需要一个起点,或者用合适的名称标记终点......但这正是建议的方法所做的。关键是你必须注意它们不要重叠。编译器不会对此采取任何措施。这是一个可扩展性限制。
【解决方案4】:

花式模板版本

由于在 C++ 中无法知道 enum 的基数,因此它被固定在一个固定的偏移量上(这里硬编码为 100,但您也可以通过它获得模板风格):

template <typename T0, typename REST>
struct enum_list : REST
{
    int base() { return 100 + REST::base(); }
    int unified(T0 value) { return int(value) + base(); }
    int separated(int value, T0 dummy) { return value - base(); }  // plus assertions?
    using REST::unified;
    using REST::separated;
};

template <typename T0>
struct enum_list<T0, void>
{
    int base() { return 0; }
    int unified(T0 value) { return int(value); }
    int separated(int value, T0 dummy) { return value; }
};

template <typename T0,        typename T1 = void, typename T2 = void, typename T3 = void,
          typename T4 = void, typename T5 = void, typename T6 = void, typename T7 = void>
struct make_enum_list {
    typedef enum_list<T0, typename make_enum_list<T1, T2, T3, T4, T5, T6, T7>::type> type;
};
template <>
struct make_enum_list<void,void,void,void> {
    typedef void type;
};

示例

enum Foo { A, B, C };
enum Bar { D, E, F };

typedef make_enum_list<Foo, Bar>::type unifier;

template <typename E>
int unify(E value)
{
    unifier u;
    return u.unified(value);
}

template <typename E>
E separate(int value)
{
    unifier u;
    return static_cast<E>(u.separated(value, E()));
}

#include <iostream>
int
main()
{
    std::cout << unify(B) << std::endl;
    std::cout << unify(F) << std::endl;
    std::cout << separate<Foo>(101) << std::endl;
    std::cout << separate<Bar>(1) << std::endl;
}

每当您添加新的enum 时,您只需将其添加到typedef make_enum_list&lt;Foo, Bar&gt;::type unifier 的列表中即可。

【讨论】:

  • 有趣的方法。这是一个具有更多可变参数的静态变量(为什么希望编译器将优化所有这些不必要的实例化?空基类,好的,但最派生的enum_list 也不需要实例化:))。此外,如果基本偏移量固定为 100,则不需要 make_enum_list。请参阅此处:ideone.com/8lL0C3 哦,少了 10 个 LoC :)
【解决方案5】:

由于枚举实际上是一个 int,因此您可以将 int 包装在一个结构中,并使用转换为/从任一枚举类型转换的方法。向枚举添加保护也有助于验证和从 int 转换回来。

enum OperationType {
    COMPARISON_OP = 0x100,
    ARITHMETIC_OP = 0x200
};

enum ComparisonType {
    UNKNOWN_COMPARISON = 0,
    LT = COMPARISON_OP,     // "<"
    GT,     // ">"
    EQ,     // "=="
    LTEQ,   // "<="
    GTEQ,   // ">="
    NEQ,    // "!="
    END_COMPARISON
};

enum ArithmeticType {
    UNKNOWN_ARITHMETIC = 0,
    ADD = ARITHMETIC_OP,    // "+"
    SUB,    // "-"
    MUL,    // "*"
    DIV,    // "/"
    MOD,    // "%"
    END_ARITHMETIC
};

struct Comparison {
    int value;

    Comparison(ComparisonType val) : value((int)val) { }
    Comparison(ArithmeticType val) : value((int)val) { }
    Comparison& operator=(ComparisonType val) {
        value = (int)val;
        return *this;
    }
    Comparison& operator=(ArithmeticType val) {
        value = (int)val;
        return *this;
    }

    ComparisonType get_comparison() const {
        if (value >= COMPARISON_OP && value < END_COMPARISON)
            return (ComparisonType)value;
        return UNKNOWN_COMPARISON;
    }
    ArithmeticType get_arithmetic() const {
        if (value >= ARITHMETIC_OP && value < END_ARITHMETIC)
            return (ArithmeticType)value;
        return UNKNOWN_ARITHMETIC;
    }
};

然后您可以将结构值与枚举常量进行比较:

Comparison cmp = ADD;
switch (cmp.value) {
    case LT:
        ...
    case ADD:
        ...
}

【讨论】:

    【解决方案6】:

    关于“铸造”枚举,我正在考虑枚举的可区分联合(有点像 Boost Variant,但具有(隐式)转换和其他专门针对枚举的便利。事不宜迟:

    假设我们有两个枚举:

    enum A { A_1, A_2, A_3, A_4 };
    enum B { B_1, B_2, B_3, B_4 };
    

    注意我不关心枚举成员的唯一性,因为我提出了一个有区别的联合。现在,我们希望能够拥有一个行为如下的类型 AorB

    A a = A_3;
    B b = B_1;
    
    AorB any;
    
    // any is isNil now
    any = b; // makes it isB
    any = a; // makes it isA
    
    if (any == A_2) // comparison is fine, because any is in `isA` now
    {
        std::cout << "Whoops, should be A_3, really\n"; // doesn't happen
    }
    
    if (any == B_2) // comparison
    {
        std::cout << "Whoops, should not match"; // doesn't happen
    }
    
    a = static_cast<A>(any); // valid cast
    b = static_cast<B>(any); // fails assertion
    

    这是我的看法:

    #include <cassert> // for assert
    #include <utility> // for std::swap
    
    struct AorB
    {
        enum Discriminant { isNil, isA, isB } discriminant;
    
        union
        {
            A valA;
            B valB;
        };
    
        AorB() : discriminant(isNil) {}
    
        A asA() const { assert(discriminant==isA); return valA; }
        B asB() const { assert(discriminant==isB); return valB; }
    
        explicit operator A() const { return asA(); }
        explicit operator B() const { return asB(); }
    
        /*explicit*/ AorB(A valA) : discriminant(isA), valA(valA) {}
        /*explicit*/ AorB(B valB) : discriminant(isB), valB(valB) {}
    
        friend void swap(AorB& a, AorB& b) {
            auto tmp = a; 
            a.discriminant = b.discriminant;
            a.safe_set(b.safe_get());
    
            b.discriminant = tmp.discriminant;
            b.safe_set(tmp.safe_get());
        }
    
        AorB& operator=(AorB implicit_conversion) {
            swap(implicit_conversion, *this);
            return *this;
        }
    
        bool operator==(AorB other) const {
            return 
                discriminant == other.discriminant && 
                safe_get()   == other.safe_get();
        }
    
      private:
        void safe_set(int val) {
            switch(discriminant) {
                case isA: valA = static_cast<A>(val); break;
                case isB: valB = static_cast<B>(val); break;
                case isNil: break;
            }
        }
        int safe_get() const {
            switch(discriminant) {
                case isA: return valA;
                case isB: return valB;
                case isNil: 
                default:  return 0;
            }
        }
    };
    

    看到它live on Coliru,打印:

    main.cpp:20: B AorB::asB() const: Assertion `discriminant==isB' failed.
    

    【讨论】:

      【解决方案7】:

      所以我最近对预处理器做了一些类似的事情,我知道这个答案迟了大约 2 年,但仍然如此。

      基本上我有各种非连续定义的枚举类型,我希望它们能够相互扩展,因此我编写了以下预处理器指令:

      #define START_ENUM(name,extends)\
      namespace name##_ns {\
      enum name\
      { BASE = extends::LAST  + 1
      #define DEF_ENUM(name) , name
      #define END_ENUM(name) \
      ,LAST\
      };};\
      using namespace name##_ns;
      

      枚举是在它们自己的命名空间中创建的,以避免 LAST 和 BASE 的多个定义。如果您不喜欢命名空间污染,您可以将它们切换为枚举类,但它会使以后来回转换为无符号整数更加麻烦。

      您需要为要扩展的连续枚举定义一个基本枚举器,但它可以为空,具体取决于您对样式的偏好

      enum class base_action {BASE = 0, LAST = 0}
      

      可以使用指令声明后续枚举

      START_ENUM(comparison, base_enum)
      DEF_ENUM(LT)
      DEF_ENUM(GT)
      ...
      END_ENUM(comparison)
      
      START_ENUM(arithmetic, comparison)
      DEF_ENUM(ADD)
      ...
      END_ENUM(arithmetic)
      

      这只是创建链式枚举的一些语法糖。

      要组合所有这些枚举,您可能仍需要进行一些转换,我使用一个简单的结构来统一枚举

      struct EnumValue
      {
          EnumValue(unsigned int _val):myVal(_val){}
      
          //template method allows casting back to original enums and such
          template<typename T>
          T asBaseEnum()
          {
              //optional range checking
              return static_cast<T>(myVal);
          }
      
          //you could template these too if you want, or add 
          //a templated conversion operator instead 
          //(template<typename T> operator T()) 
          //but I personally don't bother
          operator=(unsigned int _val){myVal = _val}
          operator==(unsigned int _val){myVal == _val}
      
      }
      

      【讨论】:

        【解决方案8】:

        我不太清楚你想要“转换组合的枚举”是什么意思,但是为了允许枚举组合,你使用了一个位域:

        enum Comparison
        {
            LT = 0x0001,     // "<"
            GT = 0x0002,     // ">"
            EQ = 0x0004,     // "=="
            LTEQ = 0x0005,   // "<=" - combines LT and EQ
            GTEQ = 0x0006,   // ">=" - combines GT and EQ
            NEQ = 0x0008     // "!="
        };
        

        由于其中一些不能合并在一起(例如,不能同时是 LT 和 GT),您可以调整位域以防止这种情况发生。

        编辑:

        因为看起来你正在寻找一些稍微不同的东西:

        如果你想合并枚举,你可以使用 Ben Jackson 已经描述的方法。另一种方法是这样做:

        enum Comparison
        {
            LT,
            ...
            NEG
        };
        
        enum Logical
        {
            AND,
            OR,
            ...
        };
        
        enum MyNewCombination
        {
            LessThan = LT,
            ...
            NotEqual = NEG,
            And = AND,
            Or = OR,
            ...
        };
        

        这将有效地将所有现有枚举移动到新的MyNewCombination 枚举中。对于有效范围,您可以将 MyNewCombination 枚举转换为 ComparisonLogical 枚举。

        【讨论】:

        • 我不想将这些作为标志合并在一起。我有几个单独的枚举,我有兴趣将它们合并为一个。
        • 在这种情况下,您可以将它们合并到一个枚举中,或者将它们链接起来(Ben Jackson 如下所示)。
        • 我希望有第三种选择,可能使用一些切割器模板和operator int()
        • 如果不重写或添加东西,你不会变得更加优雅,重载强制转换运算符不仅不会优雅,还会降低代码的可读性。
        • 这种技术能否确保 MyNewCombination 中的值是唯一的,编译器不会先将值分配给两个子枚举,然后在第三个子枚举上出现问题?
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2020-03-27
        • 1970-01-01
        • 1970-01-01
        • 2014-06-21
        • 2010-11-26
        • 2021-05-21
        • 1970-01-01
        相关资源
        最近更新 更多