Just Software Solutions

Using Enum Classes as Bitfields

Thursday, 29 January 2015

C++11 introduced a new feature in the form of scoped enumerations, also referred to as enum classes, since they are introduced with the double keyword enum class (though enum struct is also permissible, to identical effect). To a large extent, these are like standard enumerated types: you can declare a list of enumerators, which you may assign explicit values to, or which you may let the compiler assign values to. You can then assign these values to variables of that type. However, they have additional properties which make them ideal for use as bitfields. I recently answered a question on the accu-general mailing list about such a use, so I thought it might be worth writing a blog post about it.

Key features of scoped enumerations

The key features provided by scoped enumerations are:

  • The enumerators must always be prefixed with the type name when referred to outside the scope of the enumeration definition. e.g. for a scoped enumeration colour which has an enumerator green, this must be referred to as colour::green in the rest of the code. This avoids the problem of name clashes which can be common with plain enumerations.
  • The underlying type of the enumeration can be specified, to allow forward declaration, and avoid surprising consequences of the compiler's choice. This is also allowed for plain enum in C++11. If no underlying type is specified for a scoped enumeration, the underlying type is fixed as int. The underlying type of a given enumeration can be found using the std::underlying_type template from the <type_traits> header.
  • There is no implicit conversion to and from the underlying type, though such a conversion can be done explicitly with a cast.

This means that they are ideal for cases where there is a limited set of values, and there are several such cases in the C++ Standard itself: std::errc, std::pointer_safety, and std::launch for example. The lack of implicit conversions are particularly useful here, as it means that you cannot pass raw integers such as 3 to a function expecting a scoped enumeration: you have to pass a value of the enumeration, though this is of course true for unscoped enumerations as well. The lack of implicit conversions to integers does mean that you can overload a function taking a numeric type without having to worry about any potential ambiguity due to numeric conversion orderings.

Bitmask types

Whereas the implicit conversions of plain enumerations mean that expressions such as red | green and red & green are valid if red and green are enumerators, the downside is that red * green or red / green are equally valid, if nonsensical. With scoped enumerations, none of these expressions are valid unless the relevant operators are defined, which means you can explicitly define what you want to permit.

std::launch is a scoped enumeration that is also a bitmask type. This means that expressions such as std::launch::async | std::launch::deferred and std::launch::any & std::launch::async are valid, but you cannot multiply or divide launch policies. The requirements on such a type are defined in section 17.5.2.1.3 [bitmask.types] of the C++ Standard, but they amount to providing definitions for the operators |, &, ^, ~, |=, &= and ^= with the expected semantics.

The implementation of these operators is trivial, so it is easy to create your own bitmask types, but having to actually define the operators for each bitmask type is undesirable.

Bitmask operator templates

These operators can be templates, so you could define a template for each operator, e.g.

    template<typename E>
    E operator|(E lhs,E rhs){
        typedef typename std::underlying_type<E>::type underlying;
        return static_cast<E>(
            static_cast<underlying>(lhs) | static_cast<underlying>(rhs));
    }

Then you could write mask::x | mask::y for some enumeration mask with enumerators x and y. The downside here is that it is too greedy: every type will match this template. Not only would you would be able to write std::errc::bad_message | std::errc::broken_pipe, which is clearly nonsensical, but you would also be able to write "some string" | "some other string", though this would give a compile error on the use of std::underlying_type, since it is only defined for enumerations. There would also be potential clashes with other overloads of operator|, such as the one for std::launch.

What is needed is a constrained template, so only those types which you want to support the operator will match.

SFINAE to the rescue

SFINAE is a term coined by David Vandevoorde and Nicolai Josuttis in their book C++ Templates: The Complete Guide. It stands for "Substitution Failure is Not an Error", and highlights a feature of expanding function templates during overload resolution: if substituting the template parameters into the function declaration fails to produce a valid declaration then the template is removed from the overload set without causing a compilation error.

This is a key feature used to constrain templates, both within the C++ Standard Library, and in many other libraries and application code. It is such a key feature that the C++ Standard Library even provides a library facility to assist with its use: std::enable_if.

We can therefore use it to constain our template to just those scoped enumerations that we want to act as bitmasks.

    template<typename E>
    struct enable_bitmask_operators{
        static constexpr bool enable=false;
    };

    template<typename E>
    typename std::enable_if<enable_bitmask_operators<E>::enable,E>::type
    operator|(E lhs,E rhs){
        typedef typename std::underlying_type<E>::type underlying;
        return static_cast<E>(
            static_cast<underlying>(lhs) | static_cast<underlying>(rhs));
    }

If enable_bitmask_operators<E>::enable is false (which it is unless specialized) then std::enable_if<enable_bitmask_operators<E>::enable,E>::type will not exist, and so this operator| will be discarded without error. It will thus not compete with other overloads of operator|, and the compilation will fail if and only if there are no other matching overloads. std::errc::bad_message | std::errc::broken_pipe will thus fail to compile, whilst std::launch::async | std::launch::deferred will continue to work.

For those types that we do want to work as bitmasks, we can then just specialize enable_bitmask_opoerators:

    enum class my_bitmask{
        first=1,second=2,third=4
    }:
    template<>
    struct enable_bitmask_operators<my_bitmask>{
        static constexpr bool enable=true;
    };

Now, std::enable_if<enable_bitmask_operators<E>::enable,E>::type will exist when E is my_bitmask, so this operator| will be considered by overload resolution, and my_bitmask::first | my_bitmask::second will now compile.

Final code

The final code is available as a header file along with a simple example demonstrating its use. It has been tested with g++ 4.7, 4.8 and 4.9 in C++11 mode, and with MSVC 2012 and 2013, and is released under the Boost Software License.

Posted by Anthony Williams
[/ cplusplus /] permanent link
Tags: , ,

| Stumble It! stumbleupon logo | Submit to Reddit reddit logo | Submit to DZone dzone logo

Comment on this post

If you liked this post, why not subscribe to the RSS feed RSS feed or Follow me on Twitter? You can also subscribe to this blog by email using the form on the left.

19 Comments

> The lack of implicit conversions are particularly useful here, as it means that you cannot pass raw integers such as 3 to a function expecting a scoped enumeration: you have to pass a value of the enumeration

This is true even for plain enumerations though. The difference is in lack of implicit casts in the other direction.

by Arseny Kapoulkine at 15:47:52 on Thursday, 29 January 2015

Agreed. I have clarified the section.

by Anthony Williams at 16:02:59 on Thursday, 29 January 2015

I have the similar solution in my project, but based on boost.preprocessor and with the operator << implementation.

by Serge Kork at 22:27:41 on Friday, 30 January 2015

The great solution, but I would rename trait to is_bitmask_enum and derive its specifications from std::true_type (where the primary template is derived from std::false_type). I.e. the same way as standard trait is_error_code_enum is implemented (http://en.cppreference.com/w/cpp/io/io_errc/is_error_code_enum).

by Andrei Upadyshev at 12:38:39 on Saturday, 31 January 2015

Shame on my hastle. Please read specialization instead of specification and the right link is http://en.cppreference.com/w/cpp/error/error_code/is_error_code_enum

by Andrei Upadyshev at 13:02:58 on Saturday, 31 January 2015

This is very useful. The only problem I had with the idea of specializing a template is that the template must be specialized in the same namespace as the original template, so this fails:

<pre><code class="language-cplusplus"> namespace foo { enum class my_bitmask { ... }; template&lt;&gt; struct enable_bitmask_operators&lt;my_bitmask&gt;{ static constexpr bool enable=true; }; } </code></pre>

I would have to close my namesspace and put the enable_bitmask_operators specialization at global scope.

In my code I have now instead used a constexpr function:

<pre><code class="language-cplusplus"> template&lt;typename E&gt; constexpr bool enable_bitmask_operators(E) { return false; }

template&lt;typename E&gt; typename std::enable_if&lt;enable_bitmask_operators(E()),E&gt;::type operator|(E lhs,E rhs){ </code></pre>

So now I can place my overloaded function at namespace scope and ADL will find it.

<pre><code class="language-cplusplus"> namespace foo { enum class my_bitmask { ... }; constexpr bool enable_bitmask_operators(my_bitmask){ return true; } } </code></pre>

We'll see how well my code makes it into the comments :)

by Jay Miller at 16:08:19 on Monday, 09 February 2015

Yes, it might make sense to name it after is_error_code_enum.

Using constexpr functions is also a nice idea.

I was thinking that one could allow for both specialization and constexpr functions: have the primary template use the constexpr function.

If the template is not specialized then the constexpr function is used. If that is not defined then there are no bitmask operators, but if it is, and it returns true then the bitmask operators are enabled.

If the template is specialized then the result is what the specialization says.

Of course, that means we are now reserving two names: the template name in the global namespace and the constexpr function name in the namespace of each enum.

by Anthony Williams at 17:10:22 on Monday, 09 February 2015

I asked a question about this topic on SO, thought you might be interested: http://stackoverflow.com/questions/28423742/getting-constexpr-functions-resolved-without-creating-parameter-objects#28423742

by Jay Miller at 14:13:44 on Tuesday, 10 February 2015

Possibly dumb question, but what's the advantage compared with std::bitset?

by Pseudonym at 11:40:03 on Thursday, 12 February 2015

std::bitset doesn't give you nice friendly names like std::launch::async or my_colour::red.

by Anthony Williams at 14:14:20 on Thursday, 12 February 2015

True, that's a bug in the API for bitset.

by Pseudonym at 02:58:27 on Friday, 13 February 2015

Thats a bad idea if the enum type does not contain all possible or'ed values as values. With enum {A=1, B=2} A|B=3 is not an enum value so or'ing them is obscure code. For that I have build a small constexpr template class that combines the enum values and returns an underlying integer.

by Udo at 10:31:18 on Monday, 07 March 2016

Udo: If your enum is intended as a bitmask then A|B is just that, and you can then test the bits with x&A and so forth. Returning an integer from combined bitmask values doesn't make it any more meaningful.

I find it *less* meaningful to return an integer: a bitmask value that holds A|B but is still the same type conveys to me the idea that this is a particular type of value, and the individual bits (which can be tested with &) have meaning, whereas an integer that holds the numerical value of A|B is much less clear. If I see an integer in a function signature then I have to read carefully the function documentation to see what it means. If I see a bitmask type then I know immediately what it means. YMMV

by Anthony Williams at 11:46:21 on Monday, 07 March 2016

I distinguish between enumerations (one of several) and enum as flags (zero or more combined).

> whereas an integer that holds the numerical value of A|B is much less clear

Therefore TEnumBits<TMyEnum> holds the value, used as parameter for the consumer. Ergo void f(TEnumBits<TMyEnum>); f(A|B) but not f(3) or TEnumBits<TMyEnum> f();

by Udo at 07:29:07 on Tuesday, 08 March 2016

I just posted something similar to a website that linked to here. Essentially my argument clarified the one above:

When you write

Colour p = Colour::red | Colour::green;

you assign a value to p that is not in the range of Colour.

This is not the case if you use the legacy C-style way of doing things, ie Colour values defined as an enumeration or as const int, and the associated bitmap defined as a separate unsigned value. Here, the values of Colour and the Colour bitmask itself are two separate (but related) concepts (note that they have two distinct types), whereas the data structures and methods you suggest tacitly unite those concepts, which in my opinion is incorrect. Instead of an enum class and associated methods, perhaps bitmasks and their values could better be expressed as a C++ class or maybe two C++ classes (one for a typesafe enum and one for a bitmask that operates on a typesafe enum). This way we can continue to separate the bitmask from the enumerated values. The end result should look like this:

Colours p = Colour::red | Colour::green;

Note that p is a Colours (plural) type, while the enum values are singular Colour.

This way if I returned Colour from a method, you would know I am returning red or green, as opposed to returning Colours, implying there could be more than one Colour represented (which is misleading to the caller and is what I would call a 'fat' interface).

by lbj at 18:20:15 on Saturday, 18 June 2016

Let me really bring home my point with an example. Consider the method:

Colour SomeClass::SomeMethod() { //perform some calculations return Colour::red; }

Now let's say somewhere else in the code someone writes:

if (obj.SomeMethod() == Colour::red || obj.SomeMethod() == Colour::green) //do something useful

This will work fine right now. But, given your design, I could easily change SomeMethod to return Colour::red | Colour::green. There is nothing forbidden about doing this, the design specifically allows for it, but the above if statement will evaluate to false when almost certainly it should continue to evaluate to true. This is because one programmer treated Colour as the enum class that it is, and the other programmer treated Colour like the bitmask that it is.

by lbj at 19:19:16 on Saturday, 18 June 2016

@lbj: You can use a class if you like. I find the enum class with bitmask operations a useful technique. If it doesn't work for you don't use it.

With regards to the set of values and direct comparisons against constants, this is an issue of documentation. If someone treats a bitmask type as if it can only have specific values and not ORed values then they are using it wrong. This is why I made the bitmask operators opt-in: they don't apply to all enum classes.

Call your enum class "Colours" if it helps so you have Colours::red and Colours::green and Colours::red | Colours::green.

by Anthony Williams at 08:22:52 on Monday, 20 June 2016

>> I find the enum class with bitmask operations a useful technique. If it doesn't work for you don't use it.

I think that the bitmask problem is worth solving, ie people need to use *something*. You solution is not without merit, and I think with some tweaks it would really be complete, and then I would certainly use it without reservation.

>> With regards to the set of values and direct comparisons against constants, this is an issue of documentation.

I can make the same claim about the original C-style bitmask solution, which was unsatisfying enough to motivate this very article. Robust solutions are tough to break/misuse and easy to debug should a break occur. The solution you provided is easy to misuse. "Please refer to the documentation" should be very much avoided if at all possible, and indeed it is very possible in this case.

Whether or not you want to address the shortcomings of this design is up to you. I hope you do because then the C++ community would have a robust solution to a common problem that I have not seen solved anywhere else (granted I have not looked that hard).

by lbj at 16:47:38 on Monday, 20 June 2016

I have a better solution (I think), you can find it in my header-only base API "Z": github.com/redcode/Z

Abstract template enumeration class:

template <class T> struct Enumeration {

T value;

inline operator bool() const {return value;}

inline operator T() const {return value;}

inline bool operator ==(T value) const {return this->value == value;} inline bool operator !=(T value) const {return this->value != value;}

inline T operator ~() const {return ~value;}

inline T operator &(T value) const {return this->value & value;} inline T operator |(T value) const {return this->value | value;} inline T operator ^(T value) const {return this->value ^ value;}

inline Enumeration &operator &=(T value) {this->value &= value; return *this;} inline Enumeration &operator |=(T value) {this->value |= value; return *this;} inline Enumeration &operator ^=(T value) {this->value ^= value; return *this;} };

#if __cplusplus >= 201103L

# define TYPED_ENUMERATION_BEGIN(Type, UnderlyingType) \ struct Type : Enumeration<UnderlyingType> { \ inline Type() {} \ Z_INLINE_MEMBER Type(UnderlyingType value) {this->value = value;} \ enum Values : UnderlyingType {

#endif

Helper macros:

#define ENUMERATION_BEGIN(Type) \ struct Type : Enumeration<int> { \ Z_INLINE_MEMBER Type() {} \ Z_INLINE_MEMBER Type(int value) {this->value = value;} \ enum {

#define ENUMERATION_END };};

Then you simply can do:

TYPED_ENUMERATION_BEGIN(WindowMode, uint8_t) RESIZABLE = 1, FULL_SCREEN = 2, PRESERVE_ASPECT_RATIO = 4 ENUMERATION_END

or

ENUMERATION_BEGIN(WindowMode) RESIZABLE = 1, FULL_SCREEN = 2, PRESERVE_ASPECT_RATIO = 4 ENUMERATION_END

by Manuel at 15:28:36 on Saturday, 22 October 2016

Add your comment

Your name:

Email address:

Person or spambot?

Your comment:

Design and Content Copyright © 2005-2017 Just Software Solutions Ltd. All rights reserved.