在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。
c++ 20类型安全Enum操作符
博士TL;
template<typename T>
requires std::is_enum_v<T> and
requires (std::underlying_type_t<T> x) {
{ x | x } -> std::same_as<std::underlying_type_t<T>>;
T(x);
}
T operator|(T left, T right)
{
using U = std::underlying_type_t<T>;
return T( U(left) | U(right) );
}
template<typename T>
requires std::is_enum_v<T> and
requires (std::underlying_type_t<T> x) {
{ x | x } -> std::same_as<std::underlying_type_t<T>>;
T(x);
}
T operator&(T left, T right)
{
using U = std::underlying_type_t<T>;
return T( U(left) & U(right) );
}
template<typename T>
requires std::is_enum_v<T> and requires (T x) { { x | x } -> std::same_as<T>; }
T & operator|=(T &left, T right)
{
return left = left | right;
}
template<typename T>
requires std::is_enum_v<T> and requires (T x) { { x & x } -> std::same_as<T>; }
T & operator&=(T &left, T right)
{
return left = left & right;
}
基本原理
使用类型特征std::is_enum,我们可以测试一些类型T是否为枚举类型。
这包括无作用域和有作用域的枚举(即enum和enum类)。
使用类型trait std::underlying_type,我们可以得到枚举的底层类型。
使用c++ 20的概念和约束,很容易为按位操作提供重载。
有作用域和无作用域
如果操作只应该重载有作用域或无作用域的枚举,std::is_scoped_enum可以用于相应地扩展模板约束。
c++ 23
在c++ 23中,我们使用std::to_underlying来更容易地将枚举值转换为其底层类型。
移动语义和完善转发
如果你遇到了一种奇怪的情况,即你的底层类型对于copy和move有不同的语义,或者它不提供复制c'tor,那么你应该使用std::forward对操作数进行完美的转发。
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位掩码类型”小节获得完整的操作符集。
下面是一个c++ 11的惰性解决方案,它不改变枚举的默认行为。它也适用于enum struct和enum class,并且是constexpr。
#include <type_traits>
template<class T = void> struct enum_traits {};
template<> struct enum_traits<void> {
struct _allow_bitops {
static constexpr bool allow_bitops = true;
};
using allow_bitops = _allow_bitops;
template<class T, class R = T>
using t = typename std::enable_if<std::is_enum<T>::value and
enum_traits<T>::allow_bitops, R>::type;
template<class T>
using u = typename std::underlying_type<T>::type;
};
template<class T>
constexpr enum_traits<>::t<T> operator~(T a) {
return static_cast<T>(~static_cast<enum_traits<>::u<T>>(a));
}
template<class T>
constexpr enum_traits<>::t<T> operator|(T a, T b) {
return static_cast<T>(
static_cast<enum_traits<>::u<T>>(a) |
static_cast<enum_traits<>::u<T>>(b));
}
template<class T>
constexpr enum_traits<>::t<T> operator&(T a, T b) {
return static_cast<T>(
static_cast<enum_traits<>::u<T>>(a) &
static_cast<enum_traits<>::u<T>>(b));
}
template<class T>
constexpr enum_traits<>::t<T> operator^(T a, T b) {
return static_cast<T>(
static_cast<enum_traits<>::u<T>>(a) ^
static_cast<enum_traits<>::u<T>>(b));
}
template<class T>
constexpr enum_traits<>::t<T, T&> operator|=(T& a, T b) {
a = a | b;
return a;
}
template<class T>
constexpr enum_traits<>::t<T, T&> operator&=(T& a, T b) {
a = a & b;
return a;
}
template<class T>
constexpr enum_traits<>::t<T, T&> operator^=(T& a, T b) {
a = a ^ b;
return a;
}
为枚举启用位操作符:
enum class my_enum {
Flag1 = 1 << 0,
Flag2 = 1 << 1,
Flag3 = 1 << 2,
// ...
};
// The magic happens here
template<> struct enum_traits<my_enum> :
enum_traits<>::allow_bitops {};
constexpr my_enum foo = my_enum::Flag1 | my_enum::Flag2 | my_enum::Flag3;