在我的项目中,我有几个类似的枚举声明;
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
枚举强制转换为Comparison
或Logical
枚举。