在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++ 11的通用解决方案,类似于soru的方案:
template <typename TENUM>
class FlagSet {
private:
using TUNDER = typename std::underlying_type<TENUM>::type;
std::bitset<std::numeric_limits<TUNDER>::max()> m_flags;
public:
FlagSet() = default;
template <typename... ARGS>
FlagSet(TENUM f, ARGS... args) : FlagSet(args...)
{
set(f);
}
FlagSet& set(TENUM f)
{
m_flags.set(static_cast<TUNDER>(f));
return *this;
}
bool test(TENUM f)
{
return m_flags.test(static_cast<TUNDER>(f));
}
FlagSet& operator|=(TENUM f)
{
return set(f);
}
};
界面可以根据口味进行改进。那么它可以这样使用:
FlagSet<Flags> flags{Flags::FLAG_A, Flags::FLAG_C};
flags |= Flags::FLAG_D;
另一个宏解决方案,但与现有的答案不同,它没有使用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,为了简洁,我没有包括它。