追求更好的位标志枚举 当然,我们在C++ 17,C++中的一个非常棒的BITFLAG接口还没有一个满意的答案。p>
我们有追求更好的位标志枚举 当然,我们在C++ 17,C++中的一个非常棒的BITFLAG接口还没有一个满意的答案。p>,c++,enums,bit-manipulation,enum-flags,C++,Enums,Bit Manipulation,Enum Flags,我们有enum,它将其成员值引入封闭范围,但会隐式转换为其基础类型,因此可以像使用位标志一样使用,但拒绝在不强制转换的情况下重新分配回enum 我们有enum类,它解决了名称范围问题,因此它们的值必须显式命名为MyEnum::MyFlag,甚至是MyClass::MyEnum::MyFlag,但它们不会隐式转换为其基础类型,因此不能作为位标志使用,而不进行无休止的来回转换 最后,我们有C中的旧位字段,例如: struct FileFlags { unsigned ReadOnly : 1;
enum
,它将其成员值引入封闭范围,但会隐式转换为其基础类型,因此可以像使用位标志一样使用,但拒绝在不强制转换的情况下重新分配回enum
我们有enum类
,它解决了名称范围问题,因此它们的值必须显式命名为MyEnum::MyFlag
,甚至是MyClass::MyEnum::MyFlag
,但它们不会隐式转换为其基础类型,因此不能作为位标志使用,而不进行无休止的来回转换
最后,我们有C
中的旧位字段,例如:
struct FileFlags {
unsigned ReadOnly : 1;
unsigned Hidden : 1;
...
};
它的缺点是没有很好的方法将自身作为一个整体进行初始化-必须使用memset或强制转换地址或类似的方法来覆盖整个值或一次初始化它,或者一次操作多个位。它还无法命名给定标志的值(与其地址相反),因此没有表示0x02的名称,而使用枚举时有这样的名称,因此使用枚举很容易命名标志组合,例如FileFlags::ReadOnly | FileFlags::Hidden
——对于位字段来说,根本没有一个好的方法来表达这么多
此外,我们还有简单的constepr
或#define
来命名位值,然后根本不使用枚举。这可以工作,但会将位值与基础位标志类型完全分离。也许这最终不是最糟糕的方法,特别是如果位标志值是结构中的constexpr
,以赋予它们自己的名称范围
struct FileFlags {
constexpr static uint16_t ReadOnly = 0x01u;
constexpr static uint16_t Hidden = 0x02u;
...
}
因此,就目前的情况来看,我们有很多技术,但没有一种技术能够真正可靠地表达我们的观点
这是一个类型,其中包含以下有效位标志,它有自己的名称范围,这些位和类型应可自由使用标准位运算符(如&^~),它们应与整数值(如0)相比较,并且任何位运算符的结果应保持命名类型,不会退化为积分
所有这些都说,在C++中,有很多尝试在C++中生成上述实体。 Windows操作系统开发团队开发了一个简单的宏,它生成C++代码,在给定的枚举类型<代码>定义的枚举标记符运算符(枚举类型)< /C>中定义必要的丢失操作符,然后定义运算符^ ^和相关的赋值运算,如“=等”< /LI>。
enable_if
元编程,允许给定枚举转换为支持缺少运算符的位标志类型,然后以静默方式返回bit_flags flags
,然后flags
具有按位语义。这无法做到的是允许枚举基直接正确地处理按位运算符,因此即使在使用位标志时也不能说EnumType::ReadOnly | EnumType::Hidden
,因为基础枚举本身仍然不支持必要的运算符。最后,我不得不做与上面#1和#2基本相同的事情,并通过要求用户为其枚举声明元类型的专门化,为各种位运算符启用operator |(EnumType,EnumType)
,例如template struct is_bitsflag_enum:std::true_type{代码>
模板结构为_bitfagg_enum:std::true#type{};
如#2和部分#3)。这些必须发生在类或结构之外,因为C++根本没有一个机制,我知道它允许我在类中声明此类。
#include <type_traits>
template<class Tag>
struct bitflag {
enum class type;
#define DEFINE_BITFLAG_OPERATOR(OP) \
friend constexpr type operator OP(type lhs, type rhs) noexcept { \
typedef typename ::std::underlying_type<type>::type underlying; \
return static_cast<type>(static_cast<underlying>(lhs) OP static_cast<underlying>(rhs)); \
} \
friend constexpr type& operator OP ## = (type& lhs, type rhs) noexcept { \
return (lhs = lhs OP rhs); \
}
DEFINE_BITFLAG_OPERATOR(|)
DEFINE_BITFLAG_OPERATOR(&)
DEFINE_BITFLAG_OPERATOR(^)
#undef DEFINE_BITFLAG_OPERATOR
#define DEFINE_BITFLAG_OPERATOR(OP) \
friend constexpr bool operator OP(type lhs, typename ::std::underlying_type<type>::type rhs) noexcept { \
return static_cast<typename ::std::underlying_type<type>::type>(lhs) OP rhs; \
} \
friend constexpr bool operator OP(typename ::std::underlying_type<type>::type lhs, type rhs) noexcept { \
return lhs OP static_cast<typename ::std::underlying_type<type>::type>(rhs); \
}
DEFINE_BITFLAG_OPERATOR(==)
DEFINE_BITFLAG_OPERATOR(!=)
DEFINE_BITFLAG_OPERATOR(<)
DEFINE_BITFLAG_OPERATOR(>)
DEFINE_BITFLAG_OPERATOR(>=)
DEFINE_BITFLAG_OPERATOR(<=)
#undef DEFINE_BITFLAG_OPERATOR
friend constexpr type operator~(type e) noexcept {
return static_cast<type>(~static_cast<typename ::std::underlying_type<type>::type>(e));
}
friend constexpr bool operator!(type e) noexcept {
return static_cast<bool>(static_cast<typename ::std::underlying_type<type>::type>(e));
}
};
// The `struct file_flags_tag` (Which declares a new type) differentiates between different
// enum classes declared
template<> enum class bitflag<struct file_flags_tag>::type {
none = 0,
readable = 1 << 0,
writable = 1 << 1,
executable = 1 << 2,
hidden = 1 << 3
};
using file_flags = bitflag<file_flags_tag>::type;
bool is_executable(file_flags f) {
return (f & file_flags::executable) == 0;
}
所以现在,我希望有一组应该限定在给定类范围内的标志,但我不能在类头中使用这些标志(例如,默认初始化、内联函数等),因为我无法启用任何允许枚举被视为位标志的机制,直到类定义的右大括号之后。或者,我可以在它们所属的类之外定义所有此类标志枚举,这样我就可以在用户类定义之前调用“将此枚举设置为按位类型”,以便在客户机类中充分利用该功能-但是现在位标志位于外部作用域中,而不是与类本身关联
这并不是世界末日——以上这些都不是。但在编写代码时,所有这些都会带来无尽的麻烦,并阻止我以最自然的方式编写代码,即使用一个给定的标志枚举,该枚举属于该客户机类内(作用域为)的特定类,但具有按位标志语义(我的方法#3几乎允许这样做——只要所有内容都由位标志包装——显式地启用所需的位兼容性)
所有这些仍然让我有一种恼人的感觉,这可能会比现在好得多
当然应该——也许是,但我还没有弄清楚——使用枚举的方法,在枚举上启用位运算符,同时允许在封闭的类范围内声明和使用它们……
有没有人有我在上面没有考虑过的wip或方法,可以让我在这个问题上“尽可能做到最好”?用基础整数类型选择实现自己的位集并不困难。t
// union only for convenient bit access.
typedef union a
{ // it has its own name-scope
struct b
{
unsigned b0 : 1;
unsigned b2 : 1;
unsigned b3 : 1;
unsigned b4 : 1;
unsigned b5 : 1;
unsigned b6 : 1;
unsigned b7 : 1;
unsigned b8 : 1;
//...
} bits;
unsigned u_bits;
// has the following valid bit-flags in it
typedef enum {
Empty = 0u,
ReadOnly = 0x01u,
Hidden = 0x02u
} Values;
Values operator =(Values _v) { u_bits = _v; return _v; }
// should be freely usable with standard bitwise operators such as | & ^ ~
union a& operator |( Values _v) { u_bits |= _v; return *this; }
union a& operator &( Values _v) { u_bits &= _v; return *this; }
union a& operator |=( Values _v) { u_bits |= _v; return *this; }
union a& operator &=( Values _v) { u_bits &= _v; return *this; }
// ....
// they should be comparable to integral values such as 0
bool operator <( unsigned _v) { return u_bits < _v; }
bool operator >( unsigned _v) { return u_bits > _v; }
bool operator ==( unsigned _v) { return u_bits == _v; }
bool operator !=( unsigned _v) { return u_bits != _v; }
} BITS;
int main()
{
BITS bits;
int integral = 0;
bits = bits.Empty;
// they should be comparable to integral values such as 0
if ( bits == 0)
{
bits = bits.Hidden;
// should be freely usable with standard bitwise operators such as | & ^ ~
bits = bits | bits.ReadOnly;
bits |= bits.Hidden;
// the result of any bitwise operators should remain the named type, and not devolve into an integral
//bits = integral & bits; // error
//bits |= integral; // error
}
}
template< typename ENUM, typename std::enable_if< std::is_enum< ENUM >::value, int >::type* = nullptr >
inline ENUM operator |( ENUM lhs, ENUM rhs )
{
return static_cast< ENUM >( static_cast< UInt32 >( lhs ) | static_cast< UInt32 >( rhs ));
}
template< typename ENUM, typename std::enable_if< std::is_enum< ENUM >::value, int >::type* = nullptr >
inline ENUM& operator |=( ENUM& lhs, ENUM rhs )
{
lhs = lhs | rhs;
return lhs;
}
template< typename ENUM, typename std::enable_if< std::is_enum< ENUM >::value, int >::type* = nullptr >
inline UInt32 operator &( ENUM lhs, ENUM rhs )
{
return static_cast< UInt32 >( lhs ) & static_cast< UInt32 >( rhs );
}
template< typename ENUM, typename std::enable_if< std::is_enum< ENUM >::value, int >::type* = nullptr >
inline ENUM& operator &=( ENUM& lhs, ENUM rhs )
{
lhs = lhs & rhs;
return lhs;
}
template< typename ENUM, typename std::enable_if< std::is_enum< ENUM >::value, int >::type* = nullptr >
inline ENUM& operator &=( ENUM& lhs, int rhs )
{
lhs = static_cast< ENUM >( static_cast< int >( lhs ) & rhs );
return lhs;
}
#include <type_traits>
template<class Tag>
struct bitflag {
enum class type;
#define DEFINE_BITFLAG_OPERATOR(OP) \
friend constexpr type operator OP(type lhs, type rhs) noexcept { \
typedef typename ::std::underlying_type<type>::type underlying; \
return static_cast<type>(static_cast<underlying>(lhs) OP static_cast<underlying>(rhs)); \
} \
friend constexpr type& operator OP ## = (type& lhs, type rhs) noexcept { \
return (lhs = lhs OP rhs); \
}
DEFINE_BITFLAG_OPERATOR(|)
DEFINE_BITFLAG_OPERATOR(&)
DEFINE_BITFLAG_OPERATOR(^)
#undef DEFINE_BITFLAG_OPERATOR
#define DEFINE_BITFLAG_OPERATOR(OP) \
friend constexpr bool operator OP(type lhs, typename ::std::underlying_type<type>::type rhs) noexcept { \
return static_cast<typename ::std::underlying_type<type>::type>(lhs) OP rhs; \
} \
friend constexpr bool operator OP(typename ::std::underlying_type<type>::type lhs, type rhs) noexcept { \
return lhs OP static_cast<typename ::std::underlying_type<type>::type>(rhs); \
}
DEFINE_BITFLAG_OPERATOR(==)
DEFINE_BITFLAG_OPERATOR(!=)
DEFINE_BITFLAG_OPERATOR(<)
DEFINE_BITFLAG_OPERATOR(>)
DEFINE_BITFLAG_OPERATOR(>=)
DEFINE_BITFLAG_OPERATOR(<=)
#undef DEFINE_BITFLAG_OPERATOR
friend constexpr type operator~(type e) noexcept {
return static_cast<type>(~static_cast<typename ::std::underlying_type<type>::type>(e));
}
friend constexpr bool operator!(type e) noexcept {
return static_cast<bool>(static_cast<typename ::std::underlying_type<type>::type>(e));
}
};
// The `struct file_flags_tag` (Which declares a new type) differentiates between different
// enum classes declared
template<> enum class bitflag<struct file_flags_tag>::type {
none = 0,
readable = 1 << 0,
writable = 1 << 1,
executable = 1 << 2,
hidden = 1 << 3
};
using file_flags = bitflag<file_flags_tag>::type;
bool is_executable(file_flags f) {
return (f & file_flags::executable) == 0;
}
#include <type_traits>
#define MAKE_BITFLAG_FRIEND_OPERATORS_BITWISE(OP, ENUM_TYPE) \
friend constexpr ENUM_TYPE operator OP(ENUM_TYPE lhs, ENUM_TYPE rhs) noexcept { \
typedef typename ::std::underlying_type<ENUM_TYPE>::type underlying; \
return static_cast<ENUM_TYPE>(static_cast<underlying>(lhs) OP static_cast<underlying>(rhs)); \
} \
friend constexpr ENUM_TYPE& operator OP ## = (ENUM_TYPE& lhs, ENUM_TYPE rhs) noexcept { \
return (lhs = lhs OP rhs); \
}
#define MAKE_BITFLAG_FRIEND_OPERATORS_BOOLEAN(OP, ENUM_TYPE) \
friend constexpr bool operator OP(ENUM_TYPE lhs, typename ::std::underlying_type<ENUM_TYPE>::type rhs) noexcept { \
return static_cast<typename ::std::underlying_type<ENUM_TYPE>::type>(lhs) OP rhs; \
} \
friend constexpr bool operator OP(typename ::std::underlying_type<ENUM_TYPE>::type lhs, ENUM_TYPE rhs) noexcept { \
return lhs OP static_cast<typename ::std::underlying_type<ENUM_TYPE>::type>(rhs); \
}
#define MAKE_BITFLAG_FRIEND_OPERATORS(ENUM_TYPE) \
public: \
MAKE_BITFLAG_FRIEND_OPERATORS_BITWISE(|, ENUM_TYPE) \
MAKE_BITFLAG_FRIEND_OPERATORS_BITWISE(&, ENUM_TYPE) \
MAKE_BITFLAG_FRIEND_OPERATORS_BITWISE(^, ENUM_TYPE) \
MAKE_BITFLAG_FRIEND_OPERATORS_BOOLEAN(==, ENUM_TYPE) \
MAKE_BITFLAG_FRIEND_OPERATORS_BOOLEAN(!=, ENUM_TYPE) \
MAKE_BITFLAG_FRIEND_OPERATORS_BOOLEAN(<, ENUM_TYPE) \
MAKE_BITFLAG_FRIEND_OPERATORS_BOOLEAN(>, ENUM_TYPE) \
MAKE_BITFLAG_FRIEND_OPERATORS_BOOLEAN(>=, ENUM_TYPE) \
MAKE_BITFLAG_FRIEND_OPERATORS_BOOLEAN(<=, ENUM_TYPE) \
friend constexpr ENUM_TYPE operator~(ENUM_TYPE e) noexcept { \
return static_cast<ENUM_TYPE>(~static_cast<typename ::std::underlying_type<ENUM_TYPE>::type>(e)); \
} \
friend constexpr bool operator!(ENUM_TYPE e) noexcept { \
return static_cast<bool>(static_cast<typename ::std::underlying_type<ENUM_TYPE>::type>(e)); \
}
// ^ The above in a header somewhere
class my_class {
public:
enum class my_flags {
none = 0, flag_a = 1 << 0, flag_b = 1 << 2
};
MAKE_BITFLAG_FRIEND_OPERATORS(my_flags)
bool has_flag_a(my_flags f) {
return (f & my_flags::flag_a) == 0;
}
};