20

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?

Mordachai
  • 9,412
  • 6
  • 60
  • 112
  • What about [std::bitset](http://en.cppreference.com/w/cpp/utility/bitset)? – François Andrieux Apr 04 '18 at 14:42
  • 1
    Maybe I have a different understanding of bitwise semantics, but `std::bitset` supports all the bitwise operators. – François Andrieux Apr 04 '18 at 14:46
  • 1
    It's not what I thought it was - and you're right - it enables bitwise ops. But then you still don't have a name-scoped set of flag-names - one still has to set that up externally and they have no semantic relationship to the type at all... no? – Mordachai Apr 04 '18 at 14:48
  • Maybe using a `struct` with `constexpr static` flag names and a `std::bitset` member is the "best of all worlds"? – Mordachai Apr 04 '18 at 14:53
  • 1
    It's not a perfect solution to your problem, but it does address some of your concerns and the `struct FileFlags` example immediately brought it to mind. I thought it was worth mentioning. – François Andrieux Apr 04 '18 at 14:54
  • 2
    @FrançoisAndrieux `std::bitset` may be wasteful in terms of size. If I have six bitflags, I probably don't want the corresponding type to take 64 bits. But e.g. MSVC and gcc currently do just that, because `std::bitset` is backed by an integral array in those implementations (despite the desired size being a template parameter). I get it, the libraries could improve this, but as a user you may be left high and dry if they don't. – Max Langhof Apr 04 '18 at 15:09
  • 1
    It seems like C++ has been dancing *around* this issue forever and a day without actually addressing the freakin' problem. Would be nice. Or if there is something I'm missing - some technique to enable bitwise semantics for a class enum within an enclosing class definition? – Mordachai Apr 04 '18 at 15:13
  • 3
    There's always the option of writing a standards proposal. Then *maybe* in C++20 you'll get your perfect solution :-) – Jesper Juhl Apr 04 '18 at 15:16
  • lol - time to ring up my good buddy Herb Sutter and look into such a thing :D – Mordachai Apr 04 '18 at 15:21
  • 1
    I use std::bitset. If its length is issue then make or use some length-optimized clone of it https://github.com/ClaasBontus/bitset2 – Öö Tiib Apr 04 '18 at 15:35
  • @Mordachai He already has metaclasses in the works, which should allow you to write the metaclasses `bitset` so you can just say `bitset FileFlags { int ReadOnly, Hidden, ...; };`. – Daniel H Apr 04 '18 at 17:05
  • @DanielH you mean Herb Sutter does? His metaclasses WIP? That'll be quite some time down the road. But it looks interesting! – Mordachai Apr 04 '18 at 19:51
  • 1
    I find this rather useful : https://github.com/rmartinho/wheels/blob/stable/include/wheels/enums.h%2B%2B – dalle Apr 06 '18 at 17:43
  • That's another nice implementation very much along the lines of the first two I mention in my OP, with some fun preprocessor macro expansion tricks :) – Mordachai Apr 07 '18 at 15:08
  • It might help if you write a short piece of example code that demonstrates the use of all of the features you want. – Tim Randall Jul 27 '18 at 15:09
  • 1
    I use https://stackoverflow.com/a/31906371/560648 / https://codereview.stackexchange.com/q/96146/2503. – Lightness Races in Orbit Feb 05 '19 at 11:38

5 Answers5

1

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;
    }
};
Artyer
  • 31,034
  • 3
  • 47
  • 75
0

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);
Red.Wave
  • 2,790
  • 11
  • 17
  • you want to give a more complete example? this looks like it needs to use a bit class to provide the correct meaning of flags and / or enable the bitwise operators. this also looks very similar to my approach (#3) of using a wrapper class to enable bitwise operations by wrapping an enum type. Fundamentally, this doesn't solve the problem any better than #1, #2, or #3, above. – Mordachai Apr 04 '18 at 18:26
  • If you add the free consexper bit function to the bitset interface, combining flags using boolean operators won't be a trouble. But an even better approach would be a variadic function that sets some bits on: ' template <typename flags_enum> constexpr bitflags<flags_enum> onbits(flags_enum ...);' – Red.Wave Apr 05 '18 at 06:42
  • You have prepared the boilerplate; you need to extend the interface by adding a few adapter/convenience functions. – Red.Wave Apr 05 '18 at 06:46
  • @Red.Wave The variadic template is a good idea. In fact, that's one of the modifications I made to Xaqq's solution (see my answer)! – Lightness Races in Orbit Feb 05 '19 at 11:45
  • @LightnessRacesinOrbit the problem is fragmented enums, min and max elements of the enum, number of possible values which in turn may be used as an option for further optimization and compactness. But again underlying_type together with numeric_limits addresses most issues. Unused bits can be set to a specfic pattern that helps detection of uninitialized bitset or other errors due to cheating/improper casting or to save enum revision number/minimal reflection. I'd rather a use trait class to deal with such issues. – Red.Wave Feb 05 '19 at 13:34
  • @Red.Wave Xaqq used a trailing "Max" enumeration to do that, though I dropped it because I wasn't worried about compactness (and neither should you be!) -- though have made me realise my choice of size for the bitset is buggy, whoops. Improper casting should only be possible with extreme hacks that will look dangerous anyway and are the cheater's fault alone. Not sure about unset bits - why not init everything to 0 i.e. "unset"? – Lightness Races in Orbit Feb 05 '19 at 14:46
  • @LightnessRacesinOrbit a fast buggy reply of mine. size is an important problem. Without proper metadata, The smallest enum type (char) would lead to a 32 byte(256 bit) long enum - mostly unused; too expensive. Very rare situations need 32 bits, many only need 3 or 4 bits(less than a byte). So the least we need is a type trait with start and end enum values. Such meta functions are best disscoverd with a currently none-existent compiler magic. Enum metadata is a controversial missing feature. A lot of other data could be considered as part of enum metadata: is it contiguous or gaps exist?... – Red.Wave Feb 05 '19 at 15:49
  • @Red.Wave Is it _really_ "too expensive"? Most things are aligned to 32-byte boundaries anyway, no? Okay you _could_ pack one into 8 bytes or perhaps 16 or 24 ... but what do you get from that, really? – Lightness Races in Orbit Feb 05 '19 at 16:14
  • @LightnessRacesinOrbit underlying_type is expensive it does not suit. Correct Impl is something like std::bitset. However, ideal is like std::bitset()> which seems imposible to achieve. – Red.Wave Feb 05 '19 at 16:32
  • @Red.Wave Expensive how? – Lightness Races in Orbit Feb 05 '19 at 17:36
  • @LightnessRacesinOrbit number of needed bits has no relation with the underlying type. It is either limited by value range or number of elements of enum both of which are normally relatively small. – Red.Wave Feb 05 '19 at 18:17
  • @Red.Wave But how does that make it _expensive_? – Lightness Races in Orbit Feb 05 '19 at 18:30
  • @LightnessRacesinOrbit assuming char as underlying type, there are 256 possible values which means bitset<256>, but actual number of enumeration is much less; let's say 11 for file access perms on linux. 11 bits(2 bytes) vs 256 bits(32 bytes). If you can't see the difference, I can't explain anymore. – Red.Wave Feb 05 '19 at 18:37
  • @Red.Wave I can see that the numbers are different, but you still haven't explained where the _expense_ comes into it, though I've asked repeatedly... What actual, practical problem do you foresee with this? "The numbers are a bit higher" isn't an answer to that question. – Lightness Races in Orbit Feb 05 '19 at 18:41
  • @LightnessRacesinOrbit numbers are not a bit higher. You are consuming ten+ times more memory than needed. The purpose of using flags is compactness of multiple fields and using less memory. You are violating the purpose. – Red.Wave Feb 06 '19 at 04:10
  • @Red.Wave I think you overestimate how packed things are in memory. If you can give me a real-world reason why a 32-byte bitset is a deal-breaker compared to a 2-byte bitset, that goes beyond obvious cases like a severely memory-constrained system with many of these things packed next to each other in memory, I'm going to have to conclude that you cannot answer my question :P For general purposes this is _worth_ the simplified code, in my view. – Lightness Races in Orbit Feb 06 '19 at 10:45
  • @LightnessRacesinOrbit in that case should one store a flag per bit - while it a lot is easier to store one flag per byte or even per int32? The obvious answer is size considerations. Have you ever used atd::vector? Many complain that it should be byte oriented rather than bit. Why has boost wasted so much human time on defining various bitset types? Having more hardware resource capacity is not an excuse for wasting them. – Red.Wave Feb 06 '19 at 10:54
  • @Red.Wave Have you ever heard that vector is well-loved and considered to have been a good idea? No, neither have I... it is widely considered to have been a premature optimisation at the cost of many maintenance and clarity problems. You're still not answering my question so let's end this here :) – Lightness Races in Orbit Feb 06 '19 at 11:03
0

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
     }
 }
Andrey Sv
  • 247
  • 3
  • 6
  • Why would someone ever want to use the `<` and `>` operators on bit sets? – Roland Illig Dec 19 '20 at 14:56
  • @RolandIllig This only cover the task explained in comment "should be comparable to integral values". Of сourse it may never be used by consumer but for fulfillment it included. Actually it is only sample for demo and can be improved – Andrey Sv Dec 19 '20 at 22:47
0

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.

James
  • 161
  • 7
0

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.

Lightness Races in Orbit
  • 378,754
  • 76
  • 643
  • 1,055