在追求美好位标志枚举

问题描述 投票:17回答:5

好了,所以我们在C ++ 17仍然没有一个满意的答案在C一个真正伟大的bitflags接口++。

我们有enum其流血其成员值进入封闭的范围,但也隐式转换到它们的基础类型,所以可以作为,如果他们是位标志,但拒绝重新分配回枚举没有铸造。

我们有enum class解决了名的范围问题,使他们的价值观必须被明确命名MyEnum::MyFlag甚至MyClass::MyEnum::MyFlag,但他们不隐式转换到其基本类型,所以不能来回用作位标志没有无尽的铸造。

最后,我们从C如旧位字段:

struct FileFlags {
   unsigned ReadOnly : 1;
   unsigned Hidden : 1;
   ...
};

其具有自身初始化作为一个整体没有什么好办法的缺点 - 一个不得不诉诸用memset或铸造的地址或类似覆盖整个价值或一次初始化所有或以其他方式操纵一次多个位。这也从不能够说出一个给定的标志的值,而不是它的地址遭受 - 所以代表0×02没有名字,而没有使用枚举当这样的名称,因此很容易与枚举命名的组合标志,如FileFlags::ReadOnly | FileFlags::Hidden-目前根本没有那么多对位字段说的好方法。

此外,我们还有简单constexpr#define命名位,然后简单地根本不使用枚举。此作品,但是从底层位标志类型完全解离的位值。也许,这就是最终还不是最坏的办法,特别是如果位标记值constexpr一个结构中,给他们自己的名称,范围有多大?

struct FileFlags {
    constexpr static uint16_t ReadOnly = 0x01u;
    constexpr static uint16_t Hidden = 0x02u;
    ...
}

因此,当前标准的,我们有很多的技术,其中没有添加到一个非常坚实的方式说

下面是它具有以下有效的位标志中有一个类型,它有自己的名称,范围,这些位和类型应与标准位运算符,如自由使用| &^〜,并且它们应当与诸如0积分值,以及任何位运算符的结果应该保持已命名的类型,而不是退化为一个整体

所有这一切都表示,有一些尝试漂浮在尝试生产上述实体在C ++的 -

  1. Windows操作系统小组开发了一种简单的宏生成C ++代码来定义在给定的枚举类型DEFINE_ENUM_FLAG_OPERATORS(EnumType)然后操作者定义的必要缺少运营商| &^〜和相关联的分配OPS如| =等
  2. “grisumbras”支持启用位标志语义与范围的枚举here,它采用enable_if元编程允许一个给定的枚举转换到支持缺少运营商,然后再返回默默的位标志型公共Git项目。
  3. 不知道上面的,我写了一个相对简单的bit_flags包装定义所有的位运算符本身,这样一方面可以使用bit_flags<EnumType> flags然后flags具有逐位的语义。这是什么不能做的就是让枚举基地实际妥善直接处理位运算符,所以使用EnumType::ReadOnly | EnumType::Hidden即使因底层枚举本身仍然不支持必要的经营者,你不能说bit_flags<EnumType>。我不得不最终做同样的事情基本上如#1和上述第2条,并通过要求用户声明专门用于一个元类型为他们的枚举如operator | (EnumType, EnumType)使得对于各种位运算符template <> struct is_bitflag_enum<EnumType> : std::true_type {};

最终,用#1,#2和#3中的问题是,它是不可能的(据我所知),以限定在枚举本身缺少运营商(如在#1)或定义必要的使能器类型(例如template <> struct is_bitflag_enum<EnumType> : std::true_type {};如在#2和在类范围部分#3)。这些一定会发生一类或结构外,作为C ++根本没有,我知道这将允许我到一个类中做出这种声明的机制。

所以,现在,我必须有一组应当范围限定于某一类标志的愿望,但我不能使用类的头内的标志(如默认的初始化,内联函数等),因为我不能让任何的机械允许枚举被视为bitflags直到结束括号类定义后。或者,我可以定义类之外的所有此类旗枚举,他们属于,这样我就可以调用“使此枚举为按位型”用户类定义之前,要在充分利用该功能客户端类 - 但现在的位标志为在外部范围,而不是相关的类本身。

这不是世界的尽头 - 以上都不是。但是,写我的代码时,这一切将导致无休止的麻烦 - 停止我从最自然的方式写它 - 与给定的标志,枚举属于一个特定的类内即(作用域)客户端类,但与逐位标志-semantics(我的方法#3,几乎让这一点 - 只要一切由bit_flags包裹 - 明确启用所需的按位兼容性)。

所有这一切仍然让我讨厌的感觉,这可能是更好的比它!

有一定应该是 - 也许只是我还没有想通出来呢 - 的方法来枚举对他们的支持位操作,同时允许他们被宣布和封闭类范围内使用...

是否有人在WIP或者我还没有考虑上面的方法,这将让我“最好的一切可能的世界”这个?

c++ enums bit-manipulation enum-flags
5个回答
1
投票

例如

// 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
     }
 }

1
投票

你可以有一个封闭类,它的枚举值作为内部友元函数。这可以在一个宏内被用于定义所需的功能,所有的一类范围内。

例如,为了避免is_bitflag_enum性状专攻,专门保持该枚举和运营商的结构体。这就好比#2,仍然不能在课堂上完成。

#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;
}

您也可以使一个单一的宏来定义每一个朋友的功能。这就好比#1,但它毕竟是一类范围之内。

#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;
    }
};

0
投票

我用enum class具有以下模板运营商:

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;
}

如果你担心上述运营商泄露到其他枚举,我想你可以在这里枚举声明,甚至只是实现它们由枚举的基础枚举同一个命名空间封装它们(我用的宏那)。一般来说,虽然,我认为矫枉过正,现在让他们我的顶级命名空间内声明的任何代码使用。


0
投票

我拿Xaqq's FlagSet on Code Review SE的方法。

关键是要引入新的类型,以用作“容器”的一个或多个接通值从选项固定列表。所述容器是围绕bitset一个封装器,需要作为输入,一个范围的枚举的实例。

它是类型安全多亏了作用域的枚举,并可以通过运算符重载委派的bitset操作执行按位样操作。而且你还可以直接使用,如果你想的范围的枚举,如果你不需要位运算或存储多个标志。

对于生产,我也做了一些修改链接的代码;那几个都在Code Review页面上留言讨论。


-1
投票

与底层的整数类型选择实现自己的bitset并不困难。与枚举的问题是,缺少适应位集所需的元信息。但仍与正确的元编程和标志使能的特征,可以有这样的语法:

flagset<file_access_enum>   rw = bit(read_access_flag)|bit(write_access_flag);
© www.soinside.com 2019 - 2024. All rights reserved.