追求更好的位标志枚举 当然,我们在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>。

  • “grisumbras”有一个公共GIT项目,用于启用作用域枚举的位标志语义,该项目使用
    enable_if
    元编程,允许给定枚举转换为支持缺少运算符的位标志类型,然后以静默方式返回
  • 在不了解上述内容的情况下,我编写了一个相对简单的bit_flags包装器,它定义了自身的所有按位运算符,因此可以使用
    bit_flags flags
    ,然后
    flags
    具有按位语义。这无法做到的是允许枚举基直接正确地处理按位运算符,因此即使在使用
    位标志时也不能说
    EnumType::ReadOnly | EnumType::Hidden
    ,因为基础枚举本身仍然不支持必要的运算符。最后,我不得不做与上面#1和#2基本相同的事情,并通过要求用户为其枚举声明元类型的专门化,为各种位运算符启用
    operator |(EnumType,EnumType)
    ,例如
    template struct is_bitsflag_enum:std::true_type{
  • 最终,#1、#2和#3的问题在于(据我所知)不可能在类范围内定义枚举本身上缺少的运算符(如#1)或定义必要的启用码类型(例如,
    模板结构为_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;
        }
    };