在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++ 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;
我想详细说明Uliwitness的回答,为c++ 98修复他的代码,并使用Safe Bool习语,因为在c++ 11以下的c++版本中缺少std::underlying_type<>模板和显式关键字。
我还修改了它,使枚举值可以是连续的,而不需要任何显式的赋值,因此您可以有
enum AnimalFlags_
{
HasClaws,
CanFly,
EatsFish,
Endangered
};
typedef FlagsEnum<AnimalFlags_> AnimalFlags;
seahawk.flags = AnimalFlags() | CanFly | EatsFish | Endangered;
然后,您可以获得原始标志值
seahawk.flags.value();
这是代码。
template <typename EnumType, typename Underlying = int>
class FlagsEnum
{
typedef Underlying FlagsEnum::* RestrictedBool;
public:
FlagsEnum() : m_flags(Underlying()) {}
FlagsEnum(EnumType singleFlag):
m_flags(1 << singleFlag)
{}
FlagsEnum(const FlagsEnum& original):
m_flags(original.m_flags)
{}
FlagsEnum& operator |=(const FlagsEnum& f) {
m_flags |= f.m_flags;
return *this;
}
FlagsEnum& operator &=(const FlagsEnum& f) {
m_flags &= f.m_flags;
return *this;
}
friend FlagsEnum operator |(const FlagsEnum& f1, const FlagsEnum& f2) {
return FlagsEnum(f1) |= f2;
}
friend FlagsEnum operator &(const FlagsEnum& f1, const FlagsEnum& f2) {
return FlagsEnum(f1) &= f2;
}
FlagsEnum operator ~() const {
FlagsEnum result(*this);
result.m_flags = ~result.m_flags;
return result;
}
operator RestrictedBool() const {
return m_flags ? &FlagsEnum::m_flags : 0;
}
Underlying value() const {
return m_flags;
}
protected:
Underlying m_flags;
};
对于像我这样的懒人来说,下面是复制粘贴的模板解决方案:
template<class T> inline T operator~ (T a) { return (T)~(int)a; }
template<class T> inline T operator| (T a, T b) { return (T)((int)a | (int)b); }
template<class T> inline T operator& (T a, T b) { return (T)((int)a & (int)b); }
template<class T> inline T operator^ (T a, T b) { return (T)((int)a ^ (int)b); }
template<class T> inline T& operator|= (T& a, T b) { return (T&)((int&)a |= (int)b); }
template<class T> inline T& operator&= (T& a, T b) { return (T&)((int&)a &= (int)b); }
template<class T> inline T& operator^= (T& a, T b) { return (T&)((int&)a ^= (int)b); }
另一个宏解决方案,但与现有的答案不同,它没有使用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,为了简洁,我没有包括它。
@Xaqq通过flag_set类提供了一种非常不错的类型安全的方式来使用枚举标志。
我在GitHub上发布了代码,使用方法如下:
#include "flag_set.hpp"
enum class AnimalFlags : uint8_t {
HAS_CLAWS,
CAN_FLY,
EATS_FISH,
ENDANGERED,
_
};
int main()
{
flag_set<AnimalFlags> seahawkFlags(AnimalFlags::HAS_CLAWS
| AnimalFlags::EATS_FISH
| AnimalFlags::ENDANGERED);
if (seahawkFlags & AnimalFlags::ENDANGERED)
cout << "Seahawk is endangered";
}
你可以像下面这样使用struct:
struct UiFlags2 {
static const int
FULLSCREEN = 0x00000004, //api 16
HIDE_NAVIGATION = 0x00000002, //api 14
LAYOUT_HIDE_NAVIGATION = 0x00000200, //api 16
LAYOUT_FULLSCREEN = 0x00000400, //api 16
LAYOUT_STABLE = 0x00000100, //api 16
IMMERSIVE_STICKY = 0x00001000; //api 19
};
并像这样使用:
int flags = UiFlags2::FULLSCREEN | UiFlags2::HIDE_NAVIGATION;
所以你不需要int类型转换,它是直接可用的。
同样,它也像枚举类一样是范围分离的