Type-safe Bitmasks in C++

Background

Before writing this, I debated with myself whether to do a straight code dump or have a little set-up for any newbies who might come across this. Ultimately I decided to not exclude beginners. If you're not one of them, feel free to skip right to the next section.

With this out of the way... If you've programmed in C++ or C for a little while, you've probably had to deal with bitmasks. For example, the second parameter of the POSIX open function is a bitmask that lets you specify various options, such as whether to create the file if it doesn't already exist.

In short, you may think of a bitmask as an integer value where each bit specifies the state of a binary option. For example, the state of an old Nintendo controller could be represented with an 8-bit bitmask, where the first and second bits represent the state of the A and B buttons respectively (i.e. whether they are pressed down or not), the third and fourth bits - start and select, and the rest represent the directional buttons.

They typical way to implement this in C programs is by bitwise OR-ing together a bunch of powers of two. For example:

#define BTN_A 0x01 /* 00000001 in binary */ #define BTN_B 0x02 /* 00000010 in binary */ #define BTN_SELECT 0x04 /* 00000100 */ #define BTN_START 0x08 /* 00001000 */ #define BTN_UP 0x10 /* 00010000 */ #define BTN_DOWN 0x20 /* 00100000 */ #define BTN_LEFT 0x40 /* 01000000 */ #define BTN_RIGHT 0x80 /* 10000000 */

If you wanted to encode the state of the controller that has both A and B buttons pressed, you'd write something like:

uint8_t controller_state = BTN_A | BTN_B;

if (controller_state & BTN_SELECT) { // ... }

Improving Bitmasks

The way of dealing with bitmasks described above is prone to a silly kind of bug - one may inadvertently OR or AND your bitmask with some value that isn't even intended to represent anything at all, and get odd behavior as a result. Admittedly, this is not something that happens every day, but if we could prevent it - why not? Plus, I get a kick out of catching potential bugs at compile time :)

Let's start by saying that we're going to represent possible options with enum values. For example:

enum class Button { A, B, Select, Start, Up, Down, Right, Left };

Note that we don't explicitly set these to be powers of two - you'll see why in a moment.

We'll define the generic type bitmask<T> to represent bitmasks. It will work like this:

enum class RamenToppings { Egg, Scallions, Seaweed, Mushrooms }; bool ThomasApproves(bitmask<RamenToppings> toppings) { // Thomas thinks ramen with no egg is a waste. return toppings & RamenToppings::Egg; } //... printf(ThomasApproves(RamenToppings::Egg | RamenToppings::Scallions) ? "Thomas likes your choice of toppings!" : "Thomas does not approve");

Now, let's dive into the implementation. It's not long at all, and I have added comments for those who might not be familiar with some newer C++ features.

#include <type_traits> template <class option_type, // The line below ensures that bitmask can only be used with enums. typename = typename std::enable_if<std::is_enum<option_type>::value>::type> class bitmask { // The type we'll use for storing the value of our bitmask should be the same // as the enum's underlying type. using underlying_type = typename std::underlying_type<option_type>::type; // This method helps us avoid having to explicitly set enum values to powers // of two. static constexpr underlying_type mask_value(option_type o) { return 1 << static_cast<underlying_type>(o); } // Private ctor to be used internally. explicit constexpr bitmask(underlying_type o) : mask_(o) {} public: // Default ctor creates a bitmask with no options selected. constexpr bitmask() : mask_(0) {} // Creates a bitmask with just one bit set. // This ctor is intentionally non-explicit, to allow for stuff like: // FunctionExpectingBitmask(Options::Opt1) constexpr bitmask(option_type o) : mask_(mask_value(o)) {} // Set the bit corresponding to the given option. constexpr bitmask operator|(option_type t) { return bitmask(mask_ | (mask_value(t))); } // Get the value of the bit corresponding to the given option. constexpr bool operator&(option_type t) { return mask_ & mask_value(t); } private: underlying_type mask_ = 0; }; // Creates a bitmask from two options, convenient for stuff like: // FunctionExpectingBitmask(Options::Opt1 | Options::Opt2 | Options::Opt3) template <class option_type, typename = typename std::enable_if<std::is_enum<option_type>::value>::type> constexpr bitmask<option_type> operator|(option_type lhs, option_type rhs) { return bitmask<option_type>{lhs} | rhs; }

That's about it. Of course, you may add some additional features (such as OR-ing together two bitmasks to get their "sum"), if you wish, but the basic idea stays the same. This introduces no runtime overhead, but might potentially save you from dumb mistakes.

Like this post? Follow this blog on Twitter for more!