组合枚举 c++



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

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()在某种程度上是要走的路。

我经常看到的是这样的:

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

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

enum Comparison
{
    LT,     // "<"
    ...
    NEQ,    // "!="
    LastComparison
};
enum Logical
{
    AND = LastComparison,
    OR,
    ...
    LastLogical
};

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

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

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

哨的模板版本

由于无法知道enum的基数C++因此它被固定的偏移量所困(这里硬编码为 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<Foo, Bar>::type unifier 中的列表中即可。

由于枚举实际上是一个 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:
        ...
}

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

假设我们有两个枚举:

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, reallyn"; // 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;
        }
    }
};

在 Coliru 上直播,打印:

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

所以我最近对预处理器做了一些类似的事情,我知道这个答案来得晚了大约 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}
}

我不太确定您想要"强制转换组合枚举"是什么意思,但为了允许枚举组合,您使用了一个位字段:

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枚举。

最新更新