Mordachai
Mordachai

Reputation: 9662

In pursuit of a better bitflag enum

Okay, so we're at C++ 17 and there still isn't a satisfactory answer to a really great bitflags interface in C++.

We have enum which bleed their member values into the enclosing scope, but do implicitly convert to their underlying type, so can be used as-if they were bit flags but refuse to reassign back into the enum without casting.

We have enum class which solves the name scope issue so that their values must be explicitly named MyEnum::MyFlag or even MyClass::MyEnum::MyFlag, but they do not implicitly convert to their underlying type, so cannot be used as bit-flags without endless casting back and forth.

And finally, we have the old bit-fields from C such as:

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

Which has the drawback of having no good way to initialize itself as a whole - one has to resort to using memset or casting the address or similar to overwrite the entire value or initialize it all at once or otherwise manipulate multiple bits at once. It also suffers from not being able to name the value of a given flag, as opposed to its address - so there is no name representing 0x02, whereas there is such a name when using enums, hence it's easy with enums to name a combination of flags, such as FileFlags::ReadOnly | FileFlags::Hidden- there simply isn't a good way to say as much for bit-fields.

In addition we still have simple constexpr or #define to name bit values and then simply not use enums at all. This works, but completely dissociates the bit values from the underlying bitflag type. Perhaps this is ultimately not the worst approach, particularly if the bit flag values are constexpr within a struct to give them their own name-scope?

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

So, as it currently stands, we have a lot of techniques, none of which add up to a really solid way to say

Here is a type which has the following valid bit-flags in it, it has its own name-scope, and these bits and type should be freely usable with standard bitwise operators such as | & ^ ~, and they should be comparable to integral values such as 0, and the result of any bitwise operators should remain the named type, and not devolve into an integral

All of that said, there are a number of attempts floating around to try to produce the above entity in C++ -

  1. The windows OS team developed a simple macro that generates C++ code to define the necessary missing operators on a given enum type DEFINE_ENUM_FLAG_OPERATORS(EnumType) which then defines operator | & ^ ~ and the associated assignment ops such as |= and etc.
  2. 'grisumbras' has a public GIT project for enabling bitflag semantics with scoped enums here, which uses enable_if meta programming to allow a given enum to convert to a bit-flag type which supports the missing operators and back again silently.
  3. Without knowing the above, I wrote a relatively simple bit_flags wrapper which defines all of the bitwise operators on itself, so that one can use a bit_flags<EnumType> flags and then flags has bitwise semantics. What this fails to do is allow the enumerated base to actually properly handle bitwise operators directly, so you cannot say EnumType::ReadOnly | EnumType::Hidden even when using a bit_flags<EnumType> because the underlying enum itself still doesn't support the necessary operators. I had to end up doing the same thing essentially as #1 and #2 above, and enabling operator | (EnumType, EnumType) for the various bitwise operators by requiring users to declare a specialization for a meta type for their enum such as template <> struct is_bitflag_enum<EnumType> : std::true_type {};

Ultimately, the problem with #1, #2, and #3 is that it is not possible (as far as I know) to define the missing operators on the enum itself (as in #1) or to define the necessary enabler type (e.g. template <> struct is_bitflag_enum<EnumType> : std::true_type {}; as in #2 and partially #3) at class scope. Those must happen outside of a class or struct, as C++ simply doesn't have a mechanism that I am aware of which would allow me to make such declarations within a class.

So now, I have the desire to have a set of flags that should be scoped to a given class, but I cannot use those flags within the class header (e.g. default initialization, inline functions, etc.) because I cannot enable any of the machinery that allows the enum to be treated as bitflags until after the closing brace for the class definition. Or, I can define all such flag-enums outside of the class that they belong to, so that I can then invoke the "make this enum into a bitwise type" before the user-class definition, to have full use of that functionality in the client class - but now the bit flags are in the outer scope instead of being associated to the class itself.

This isn't the end of the world - none of the above is. But all of it causes endless headaches when writing my code - and stops me from writing it in the most natural way - i.e. with a given flag-enum that belongs to a specific class within (scoped to) that client class, but with bitwise flag-semantics (my approach #3 almost allows this - so long as everything is wrapped by a bit_flags - to explicitly enable the needed bitwise compatibility).

All of this still leaves me with the annoying sense that this could be much better than it is!

There surely should be - and perhaps is but I haven't figured it out yet - approach to enums to enable bitwise operators on them while allowing them to be declared and used within an enclosing class scope...

Does anyone have a wip or an approach I haven't considered above, that would allow me "the best of all possible worlds" on this?

Upvotes: 21

Views: 2799

Answers (5)

Artyer
Artyer

Reputation: 40911

You can have friend functions inside of an enclosing class that takes the enum as values. This can be used within a macro to define the necessary functions, all within a class scope.

For example, to avoid the is_bitflag_enum trait to specialize, specialize a struct which holds the enums and the operators. This is like #2, and still can't be done in a class.

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

You can also make a single macro to define every single friend function. This is like #1, but it is all within a class scope.

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

Upvotes: 1

Lightness Races in Orbit
Lightness Races in Orbit

Reputation: 385385

I take the approach of Xaqq's FlagSet on Code Review SE.

The key is to introduce a new type to serve as a "container" for one or more switched-on values from a fixed list of options. Said container is a wrapper around bitset that takes, as input, instances of a scoped enum.

It's type-safe thanks to the scoped enum, and can perform bitwise-like operations via operator overload delegating to bitset operations. And you can still use the scoped enum directly if you wish, and if you don't need the bitwise operations or to store multiple flags.

For production, I did make some changes to the linked code; a few of those are discussed in comments on the Code Review page.

Upvotes: 0

James
James

Reputation: 315

I use enum class with the following templated operators:

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

If you're concerned about the above operators leaking out into other enums, I guess you could encapsulate them at the same namespace where the enum is declared, or even just implement them on an enum by enum basis (I used to use a macro for that). Generally speaking though, I've considered that overkill, and now have them declared within my top-level namespace for any code to use.

Upvotes: 0

Andrey Sv
Andrey Sv

Reputation: 257

For example

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

Upvotes: 0

Red.Wave
Red.Wave

Reputation: 4257

Implementing own bitset with underlying integer type selection is not difficult. The problem with enum is that the necessary meta information for adapting to bitset is missing. But still with proper meta programming and flag enable traits it is possible to have such syntax:

flagset<file_access_enum>   rw = bit(read_access_flag)|bit(write_access_flag);

Upvotes: 0

Related Questions