在c#中,通过[flags]属性将枚举视为标志,但在c++中实现这一点的最佳方法是什么?
例如,我想写:
enum AnimalFlags
{
HasClaws = 1,
CanFly =2,
EatsFish = 4,
Endangered = 8
};
seahawk.flags = CanFly | EatsFish | Endangered;
然而,我得到编译器错误关于int/enum转换。除了生硬的角色转换,还有更好的表达方式吗?最好,我不想依赖第三方库(如boost或Qt)的构造。
编辑:如答案中所示,我可以通过声明seahawk来避免编译器错误。标记为int。但是,我希望有某种机制来执行类型安全,这样就不能编写seahawk了。flags = HasMaximizeButton。
复制粘贴的“邪恶”宏基于一些其他的答案在这个线程:
#include <type_traits>
/*
* Macro to allow enum values to be combined and evaluated as flags.
* * Based on:
* - DEFINE_ENUM_FLAG_OPERATORS from <winnt.h>
* - https://stackoverflow.com/a/63031334/1624459
*/
#define MAKE_ENUM_FLAGS(TEnum) \
inline TEnum operator~(TEnum a) { \
using TUnder = typename std::underlying_type_t<TEnum>; \
return static_cast<TEnum>(~static_cast<TUnder>(a)); \
} \
inline TEnum operator|(TEnum a, TEnum b) { \
using TUnder = typename std::underlying_type_t<TEnum>; \
return static_cast<TEnum>(static_cast<TUnder>(a) | static_cast<TUnder>(b)); \
} \
inline TEnum operator&(TEnum a, TEnum b) { \
using TUnder = typename std::underlying_type_t<TEnum>; \
return static_cast<TEnum>(static_cast<TUnder>(a) & static_cast<TUnder>(b)); \
} \
inline TEnum operator^(TEnum a, TEnum b) { \
using TUnder = typename std::underlying_type_t<TEnum>; \
return static_cast<TEnum>(static_cast<TUnder>(a) ^ static_cast<TUnder>(b)); \
} \
inline TEnum& operator|=(TEnum& a, TEnum b) { \
using TUnder = typename std::underlying_type_t<TEnum>; \
a = static_cast<TEnum>(static_cast<TUnder>(a) | static_cast<TUnder>(b)); \
return a; \
} \
inline TEnum& operator&=(TEnum& a, TEnum b) { \
using TUnder = typename std::underlying_type_t<TEnum>; \
a = static_cast<TEnum>(static_cast<TUnder>(a) & static_cast<TUnder>(b)); \
return a; \
} \
inline TEnum& operator^=(TEnum& a, TEnum b) { \
using TUnder = typename std::underlying_type_t<TEnum>; \
a = static_cast<TEnum>(static_cast<TUnder>(a) ^ static_cast<TUnder>(b)); \
return a; \
}
使用
enum class Passability : std::uint8_t {
Clear = 0,
GroundUnit = 1 << 1,
FlyingUnit = 1 << 2,
Building = 1 << 3,
Tree = 1 << 4,
Mountain = 1 << 5,
Blocked = 1 << 6,
Water = 1 << 7,
Coastline = 1 << 8
};
MAKE_ENUM_FLAGS(Passability)
优势
仅在显式使用时应用于选定的枚举。
不使用非法的reinterpret_cast。
不需要指定底层类型。
笔记
如果使用c++ <14,将std::underlying_type_t<TEnum>替换为std::underlying_type<TEnum>::type。
另一个宏解决方案,但与现有的答案不同,它没有使用reinterpret_cast(或C-cast)在enum&t和Int&之间进行强制转换,这在标准c++中是禁止的(参见本文)。
#define MAKE_FLAGS_ENUM(TEnum, TUnder) \
TEnum operator~ ( TEnum a ) { return static_cast<TEnum> (~static_cast<TUnder> (a) ); } \
TEnum operator| ( TEnum a, TEnum b ) { return static_cast<TEnum> ( static_cast<TUnder> (a) | static_cast<TUnder>(b) ); } \
TEnum operator& ( TEnum a, TEnum b ) { return static_cast<TEnum> ( static_cast<TUnder> (a) & static_cast<TUnder>(b) ); } \
TEnum operator^ ( TEnum a, TEnum b ) { return static_cast<TEnum> ( static_cast<TUnder> (a) ^ static_cast<TUnder>(b) ); } \
TEnum& operator|= ( TEnum& a, TEnum b ) { a = static_cast<TEnum>(static_cast<TUnder>(a) | static_cast<TUnder>(b) ); return a; } \
TEnum& operator&= ( TEnum& a, TEnum b ) { a = static_cast<TEnum>(static_cast<TUnder>(a) & static_cast<TUnder>(b) ); return a; } \
TEnum& operator^= ( TEnum& a, TEnum b ) { a = static_cast<TEnum>(static_cast<TUnder>(a) ^ static_cast<TUnder>(b) ); return a; }
失去reinterpret_cast意味着我们不能再依赖x |= y语法,但是通过将这些扩展为x = x | y形式,我们就不再需要它了。
注意:你可以使用std::underlying_type来获取TUnder,为了简洁,我没有包括它。
c++标准明确讨论了这一点,请参见“17.5.2.1.3位掩码类型”部分:
http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2012/n3485.pdf
给定这个“模板”,你会得到:
enum AnimalFlags : unsigned int
{
HasClaws = 1,
CanFly = 2,
EatsFish = 4,
Endangered = 8
};
constexpr AnimalFlags operator|(AnimalFlags X, AnimalFlags Y) {
return static_cast<AnimalFlags>(
static_cast<unsigned int>(X) | static_cast<unsigned int>(Y));
}
AnimalFlags& operator|=(AnimalFlags& X, AnimalFlags Y) {
X = X | Y; return X;
}
其他的运算符也一样。
还要注意“constexpr”,如果您希望编译器能够在编译时执行操作符,则需要使用它。
如果你正在使用c++ /CLI并且希望能够分配给ref类的enum成员,你需要使用跟踪引用:
AnimalFlags% operator|=(AnimalFlags% X, AnimalFlags Y) {
X = X | Y; return X;
}
注意:此示例不完整,请参见“17.5.2.1.3位掩码类型”小节获得完整的操作符集。
也许像Objective-C的NS_OPTIONS。
#define ENUM(T1, T2) \
enum class T1 : T2; \
inline T1 operator~ (T1 a) { return (T1)~(int)a; } \
inline T1 operator| (T1 a, T1 b) { return static_cast<T1>((static_cast<T2>(a) | static_cast<T2>(b))); } \
inline T1 operator& (T1 a, T1 b) { return static_cast<T1>((static_cast<T2>(a) & static_cast<T2>(b))); } \
inline T1 operator^ (T1 a, T1 b) { return static_cast<T1>((static_cast<T2>(a) ^ static_cast<T2>(b))); } \
inline T1& operator|= (T1& a, T1 b) { return reinterpret_cast<T1&>((reinterpret_cast<T2&>(a) |= static_cast<T2>(b))); } \
inline T1& operator&= (T1& a, T1 b) { return reinterpret_cast<T1&>((reinterpret_cast<T2&>(a) &= static_cast<T2>(b))); } \
inline T1& operator^= (T1& a, T1 b) { return reinterpret_cast<T1&>((reinterpret_cast<T2&>(a) ^= static_cast<T2>(b))); } \
enum class T1 : T2
ENUM(Options, short) {
FIRST = 1 << 0,
SECOND = 1 << 1,
THIRD = 1 << 2,
FOURTH = 1 << 3
};
auto options = Options::FIRST | Options::SECOND;
options |= Options::THIRD;
if ((options & Options::SECOND) == Options::SECOND)
cout << "Contains second option." << endl;
if ((options & Options::THIRD) == Options::THIRD)
cout << "Contains third option." << endl;
return 0;
// Output:
// Contains second option.
// Contains third option.