10

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

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()是要走的路。

4

8 回答 8

20

我经常看到的是这样的:

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);}
于 2013-08-20T21:04:18.383 回答
6

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

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

enum Logical
{
    AND = LastComparison,
    OR,
    ...
    LastLogical
};
于 2013-08-20T20:50:13.550 回答
2

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

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

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

于 2013-08-20T21:24:16.450 回答
1

花式模板版本

由于无法知道enumC++ 中 a 的基数,因此它被固定在一个固定的偏移量上(这里硬编码为 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.

于 2013-08-20T22:14:37.840 回答
1

由于枚举实际上是一个 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:
        ...
}
于 2018-12-25T22:17:36.573 回答
0

关于“铸造”枚举,我正在考虑枚举的有区别的联合(有点像 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;
        }
    }
};

在 Coliru 上看到它,打印:

main.cpp:20: B AorB::asB() const: Assertion `discriminant==isB' failed.
于 2013-08-20T21:44:50.873 回答
0

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

}
于 2015-11-30T02:09:31.797 回答
-2

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

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枚举转换为 aComparisonLogical枚举。

于 2013-08-20T20:50:38.137 回答