Safe Labels in C++
by Fedor Pikus
October 1, 2007

Summary
The need to write efficient and high-performance programs in C++ make it desirable to be able to manipulate bits and groups of bits easily, efficiently, and safely. C++ provides out-of-the-box tools to accomplish the first two goals with its bitwise operations, but it does so at the expense of the third objective, safety. This article presents a solution to the problem of constraining bit operations to allow only safe and legitimate ones, and turn all invalid bit manipulations into compile-time errors. Best of all, the syntax of bit operations remains unchanged, and the code working with bits does not need to be modified, except possibly to fix errors that had as yet remained undetected.
Introducing The Catenator

Introduction

To a "real" programmer, bits are a terrible thing to waste. And many programs present opportunities to make the most use out of single bits: we often have to deal with large collections of objects that have complex states or attributes composed out of many yes-or-no questions. For most efficient memory use such states are often represented with a bit field, an integer word whose every bit is used independently to store one individual yes-or-no status. C++ provides efficient bitwise operators to manipulate such bit field statuses, however, it does little to help the programmer detect and avoid incorrect use of the bit fields. Encapsulating bit fields in classes and providing access methods to individual bits, set_state_X() and get_state_X(), can enforce correct use, but is tedious (imagine writing 32 set functions with unique self-explanatory names, one for every bit) and often comes at the expense of efficiency because we can no longer manipulate several bits at once. In addition, while this approach may work for new programs, converting existing programs which use "bare" integers as bit fields to use encapsulated classes can be a lot of work.

In this article we describe a solution that ensures safe and correct use of bit fields with errors detected at compile time. There are several such bitset libraries out there, ranging from collections of macros[1] to sophisticated templates[2] and everything in between[3][4]. The distinguishing feature of the solution proposed here is that it requires minimal changes to the code that uses bit fields: no change to the client code is needed, only bit field declarations must be changed. The "bitset class" we introduce supports the traditional bit operators and they behave in the expected manner. There may be several reasons to favor such solution, but the main one is that you can just drop the library into existing code, edit declarations of bit fields, and gain type safety.

Sounds simple enough, but the problem is harder than it appears at the first glance: bit fields support many operations and conversions, some are legitimate and desirable and must remain supported, while others are invalid and should become compilation errors. In particular, we would like existing code of the form

if ( state_bits & STATE_X ) .. 

to compile if and only if the bit field state_bits can properly include STATE_X, otherwise we would like to see a compilation error. Teaching the compiler to recognize all the subtle differences between valid and invalid conversions and operations is what we will be working on for the rest of this article.

Naked Bits

Imagine that Dogbert set out to write a program to track all cats and dogs in the world in preparation for the day he takes it over. Each pet is represented by an object of class Cat or Dog, and contains its current state, among other things. Considering the incredible number of such objects, the programmer would likely use a bit field for the state, and provide helpful named constants to access individual bits:

const unsigned int CAT_SLEEPING = 0x0001; 
const unsigned int CAT_PURRING  = 0x0002;  
const unsigned int CAT_PLAYING  = 0x0004;  
...  
class Cat {  
  public:  
  unsigned int state;  
};  

const unsigned int DOG_BARKING  = 0x0001;  
const unsigned int DOG_CHEWING  = 0x0002;  
const unsigned int DOG_DROOLING = 0x0004; 
... 
class Dog { 
  public: 
  unsigned int state; 
}; 

This makes it very easy and efficient to manage states, one by one or several at once, using bit-wise operators.

bool is_happy( Cat& cat ) { 
  return cat.state & ( CAT_SLEEPING | CAT_PURRING ); 
} 

bool is_noisy( Dog& dog1, Dog& dog2 ) { 
  if ( ( dog1.state | dog2.state ) & DOG_BARKING ) { 
    dog1.state |= DOG_BARKING; 
    dog2.state |= DOG_BARKING; 
    return true; 
  } else { 
    return false; 
  } 
} 

You can see even from this simple example some of the many operations that can be done with bit fields. The explicitly written bitwise operations are obvious, but there is also a hidden implicit conversion to bool in the if statement. While useful and necessary, implicit conversions are anathema to type safety, and it will take considerable effort to restrict our bit fields to convert only when we want them to.

Unfortunately, mismanaging states can be just as easy:

Cat& pet =
  find_cat(); if ( pet.state & DOG_BARKING ) ... // Meow? 

In fact, there is no compile-time protection from using any bit constants with any incompatible bit fields, because they are incompatible only to the programmer but not to the compiler. Note that such little niceties as access methods, set_state(unsigned int) and check_state(unsigned int), or declaring named constants inside the class they are supposed to belong to, while useful for other reasons, do nothing to address the problem of using incompatible bit fields in the same operation.

What Doesn't Work

When confronted with this problem for the first time, a programmer might declare new type for each state with typedef:

typedef unsigned int Cat_state; 
const Cat_state CAT_SLEEPING = 0x0001; 
const Cat_state CAT_PURRING = 0x0002; 
const Cat_state CAT_PLAYING = 0x0004; 
... 
class Cat { 
  public: 
  Cat_state state; 
}; 

typedef unsigned int Dog_state; 
const Dog_state DOG_BARKING = 0x0001; 
const Dog_state DOG_CHEWING = 0x0002; 
const Dog_state DOG_DROOLING = 0x0004; 
... 
class Dog { 
  public: 
  Dog_state state; 
}; 

Unfortunately, this does not change anything at all: typedef in fact does not introduce a new type, merely an alias for an existing one. The next thing most programmers try is using enum to declare both bit fields and named constants:

enum Cat_state { 
CAT_SLEEPING = 0x0001, 
CAT_PURRING  = 0x0002,  
CAT_PLAYING  = 0x0004,  
... };  
class Cat {  
  public:  

  Cat_state state; 
}; 
enum Dog_state { 
  DOG_BARKING = 0x0001, 
  DOG_CHEWING = 0x0002, 
  DOG_DROOLING = 0x0004, 
  ... 
}; 

class Dog { 
  public: 
  Dog_state state; 
}; 

This makes it abundantly clear to the programmer that cat and dog states are not meant to be used together. Alas, the compiler gets no such message: while different enums are in fact different types, they are all implicitly convertible to integer types:

Cat& pet = find_cat(); 
if ( pet.state & DOG_BARKING ) ... // Hiss! Still compiles 

Even if we had type-safe enums (as proposed for the C++0x standard), it would not solve our problem: we actually want our enums to convert to integers, because combinations of flags cannot be represented by enums. For example, DOG_BARKING | DOG_DROOLING is a set of flags that is outside of the enum list, but we want to be able to represent it. What we really need is for enums to convert to integers, but to special integers that are not convertible to built-in integers or to integers generated from other enums. In other words, we need an enum and an integer type that together represent a particular bit field, such as cat state, which can be converted to each other but not to other types.

The above requirements for the kind of type safety we need are quite unique. For example, Boost has a strong typedef[5] which could be used to create a unique integer, but it does not restrict which operations can be done on the new type, and, as we will see later, having separate but related types for bit constants and bit fields allows us to do more static checking.

Having discovered that trivial changes are inadequate and available "strong types" or bit libraries do not address the problem, the programmer is left with two options. The first, simplest, and probably most frequently chosen, is to abandon the idea and try to find bugs in bit field use during debugging. The second option is to unleash the full power of C++ at the problem and convert bit fields which should be different and incompatible into types that are different and incompatible; in other words, into classes. Unfortunately, such changes often require that all client code which manipulates the bits be rewritten, which in turn leads the programmer back to the first option, especially if he has to deal with a large body of "legacy" code: bit fields, when they are used at all, tend to be used everywhere, and making massive changes to old crufty code takes a long time, is error-prone, and provides no immediate payoff (at least none that your manager can see). Beware that after reading this article you will no longer have any excuses.

The Solution

The solution presented below prevents any errors resulting from use of incompatible bit fields and bit constants together in bit operations, comparisons, or assignments. Any such invalid use becomes a compile-time error. In addition, the only changes required are in declarations of the bit fields and definitions of the named bit constants. The client code which uses the bits does not need to be rewritten at all. Here is how bit field and bit constant declarations will look like after the change:

BIT_FIELD( unsigned int ) Cat_state; 
BIT_CONST( Cat_state, CAT_SLEEPING, 1 );// 0x0001 -1st bit is set 
BIT_CONST( Cat_state, CAT_PURRING, 2 );// 0x0002 -2nd bit is set 
BIT_CONST( Cat_state, CAT_PLAYING, 3 );// 0x0004 -3rd bit is set 
... 
class Cat { 

  public: 
  Cat_state state; 
}; 

BIT_FIELD( unsigned int ) Dog_state; 
BIT_CONST( Dog_state, DOG_BARKING, 1 ); 
BIT_CONST( Dog_state, DOG_CHEWING, 2 ); 
BIT_CONST( Dog_state, DOG_DROOLING, 3 ); 
... 
class Dog { 

  public: 
  Dog_state state; 
}; 

It is also possible to make the bit numbering, currently done by the third argument to the BIT_CONST macro, to be implicit, but the syntax above affords more control, including the ability to skip some bits, and makes it easy to figure out which bits are used for what (may be useful for interfacing with C programs or hardware). Another approach is to use the technique similar to the suggested BOOST_ENUM macro [10] and list all names in a single long macro call:

BIT_FIELD( unsigned int, Cat_state, (CAT_SLEEPING) (1) (CAT_PURRING) (2) (CAT_PLAYING) (3) ); 

The client code does not change at all. Unless it needs to be changed, that is:

Cat& pet = find_cat(); 
bool happy = pet.state & ( CAT_SLEEPING | CAT_PURRING ); 
if ( pet.state & DOG_BARKING ) ... // Growl! Compile error! 

All attempts to use incompatible bits together are now caught by the compiler. In addition, as a free bonus, several other kinds of bit field misuse are caught by the safe bit fields and become compile-time errors:

if ( cat.state & CAT_SLEEPING == 0 ) ...// Operator precedence 
if ( dog.state && DOG_BARKING ) ... // Logical && 

The Code

Now let's see the magic which powers the safe bit field. The code uses two class templates, Safe_bit_field and Safe_bit_const, to represent bit fields themselves and the named constants used to initialize them. Why isn't one template enough? After all, the named constant is just a bit field with a particular value, usually with one bit set. It all has to do with the requirement that the client code need not be modified. Specifically, this kind of code:

if ( dog.state & DOG_BARKING ) ... 

It is obvious that in order for such code to compile, the bit field must be implicitly convertible to bool. This conversion is a major source of problems when it comes to assuring that incompatible bit fields cannot be mixed together, and we will have to deal with that later. Note that some of these problems could be solved also by making the implicit conversion to void* instead of bool, or even to an "unspecified boolean type," which is basically a pointer to some internal type not visible to the client code. Now we are more concerned with another context for such implicit conversion:

if ( dog.state && DOG_BARKING ) ... 

One little character can make a lot of difference! This is almost certainly an error, and it would be nice for the compiler to catch it. However, the only way it can be done if a named bit constant like DOG_BARKING itself is not implicitly convertible to bool. Therefore, it has to be of a different type than the bit field.

Let's start with the template for the bit field, Safe_bit_field (Listing 1). It has two template arguments. The second is obvious: word_t is the type of the word used to store the bits. It should be one of the integer types, large enough to store all the bits we need. Often it will be unsigned long, but if you only need, say, 8 bits to represent the state, there is no reason not to use unsigned char. The first template argument, unique_index, is what makes different bit fields into different types. Obviously, we have to make sure that the values of the index are, well, unique, but that can be automated with some preprocessor macros, as we will see.

Notice that Safe_bit_field has two sets of copy constructors, as well as assignment, comparison, and bitwise operators: one for operations involving only other Safe_bit_field objects and the other for operations involving bit constants, Safe_bit_const. Why not just rely on the implicit conversion from Safe_bit_const provided by one of the Safe_bit_field constructors? Because only one user-defined conversion is allowed in a chain of conversions, and we may need to save it for that implicit conversion to bool, otherwise any code which needs both a conversion from bit constants to bit fields and a conversion to bool will not compile.

Next comes the implicit conversion operator bool which we have already mentioned, and bit shift operators. The latter are not often needed, but they can be useful in loops which iterate over bit constants and need a way to increment them.

For convenience, we declare Safe_bit_field::const_t, a typedef for the bit constant type corresponding to the bit field type. Unfortunately, typedef cannot be used in the friend declaration, so we have to repeat the full type once more. Another convenient typedef provides easy access to the integer type used to store the bits.

Bits, Protected

So far we have dealt with the interface which is designed to make safe bit fields compile and work in any context where the unsafe bit fields, or plain integers, might be legitimately used. Now let's look at the code which should prevent safe bit fields from compiling in any context where we don't want them to be used. We are primarily concerned with code like this:

Cat& cat = find_cat(); 
Dog& dog = find_dog(); 
if ( cat.state & DOG_BARKING ) ... 
if ( cat.state == dog.state ) ... 

Note, however, that there are no valid bitwise or comparison operations involving safe bit fields, aside from the ones involving bit fields of the same type and respective bit constants, and we already declared these in the public interface of the Safe_bit_field class. So we might as well disallow comparisons and bitwise operations involving all other types and not worry if some unintended conversion can circumvent our carefully crafted protection. So we declare some template comparison and bitwise operators which can instantiate for any type of the right-hand-side argument but will not compile. Since there are not meant to be used, we can declare them private, but that does not yet guarantee that no code can ever get to these operators. To be absolutely safe, we make sure that even if instantiated, these operators do not compile. There are several ways in which this can be accomplished; we have chosen to use an incomplete type: variables of such type cannot be declared.

There is a subtle point here: the incomplete type itself must be a template. This is because it is used inside a function template, and the standard has something interesting to say about that: if the type is incomplete at the point of template definition, the template is illegal, but compilers are allowed to accept or reject such code (§14.6). The standard-compliant way to is to defer binding to the point of instantiation by making the incomplete type itself a template. In the Safe_bit_field class, we use the following template struct (which must not be defined):

template <typename> struct Forbidden_conversion; 

Any attempt to perform a forbidden conversion on a safe bit field object will lead to an attempted instantiation of this template, and a compiler error. The exact error you get varies from compiler to compiler, but the error message will almost certainly mention "Forbidden_conversion" and probably even the other type involved in the conversion. Other types of compile-time asserts are available [6-9], choosing one is a matter of personal preferences.

The template member operators catch errors when the first argument to a binary operator is a safe bit field, but they don't work when the first argument is an integer and the second one is a bit field, because the bit field converts to bool and the operator is performed on two integers:

Cat& cat = find_cat(); 
Dog& dog = find_dog(); 
if ( !cat.state & dog.state ) ... 

The first bit field is converted to bool to enable logical "not" operator, then goes into the bitwise "and" as a bool. Since the first argument to the operator is a built-in type, no member functions are invoked, therefore, we have to catch these errors with non-member template operators, and so we define them after the Safe_bit_field class itself (If we do not define them as members, an existing non-member operator & must be used, and one indeed will be found after the second argument is converted to bool, but it's not the one we want). These operators use the same incomplete Forbidden_conversion type and, together with member operators will not allow a safe bit field to be used with anything other than a bit field of the same type in any binary bitwise or comparison operator.

Constant Bits

The Safe_bit_field class handles all the operations with bit fields except for one, the initialization: it can only be constructed from a Safe_bit_const object, a named bit field constant. Listing 2 shows the Safe_bit_const class template. It is overall very similar to the Safe_bit_field class, but there are two important differences.

The first is the constructor, or, rather a static template factory "constructor" member which creates a bit field with one bit set. The position of the bit is given by the template parameter: bit 1 is the junior bit, so make_bit_const<1>() returns 1. Bit index 0 is a special case and returns a bit field with no bits set, or 0.

The factory constructor will not compile if the bit index is outside of the valid range for the integer type used to store the bit field. The CTASSERT macro implements a well-known "compile-time assert"[6] and will cause a compile-time error if the asserted expression is false. You may notice that there is a redundant test for inside the shift operator, even though the shift can never be evaluated for i == 0. It is not required, but some compilers see shift by i-1 and complain that for i == 0 the number is invalid, without checking whether or not the shift is actually evaluated.

The second difference is the lack of the conversion to bool operator. There is no need to test values of named bit constants. Because the implicit conversion to bool does not exist, protecting the Safe_bit_const class from improper use is considerably easier, and some of the template operators which were needed for Safe_bit_field class are not repeated here.

Macro Convenience

We now have everything we need to use safe bit fields and to create named bit constants. The latter, however, is quite verbose. We can make bit field declarations much more compact with the help of several macros (Listing 3). Also, since the code which uses the bit fields does not have to change when safe bit fields are used, we can make the declarations conditional by changing one #define switch between safe bit fields and integer bit fields. This may be useful if your application must be ported to a compiler which does not support some of the C++ features used by safe bit fields. All macros are conditionally defined to use the Safe_bit_field classes if SAFE_BIT_FIELD is defined. Otherwise, the macros fall back on the use of typedefs and integer constants.

The first macro helps to declare new bit field types:

BIT_FIELD( unsigned long ) cat_state; 

This creates a typedef cat_state for Safe_bit_field<unique_index, unsigned long> where unique index is the current line number. Since line numbers (__LINE__) are counted separately for all header files, this ends up being the same type in all files using the header which defines cat_state. Note, however, that with this macro we cannot declare two bit fields on the same line. There are various ways to overcome this limitation, at least to a degree. For example, we can use the type name itself as a unique tag:

#define BIT_FIELD( word_t, type ) \ 
struct name2(UNIQ_, type) {}; \ 
typedef Safe_Bit_Field<name2(UNIQ_, type), word_t> type 

The declaration of the new type would change so the type is a macro argument:

BIT_FIELD( unsigned long, cat_state ); 

The second macro helps to declare static bit constants:

BIT_CONST( cat_state, CAT_PURRING, 1 ); 

This creates a new bit constant object named CAT_PURRING of type cat_state which represents the label with the 1st (junior) bit set.

The third macro helps to declare complex bit field constants which are combination of several bit constants and have several bits set:

BIT_CONSTS( cat_state, CAT_HAPPY ) = CAT_PURRING | CAT_SLEEPING; 

The last macro helps to declare the maximum number of one-bit constants for a given type:

static const size_t cat_state_count = BIT_FIELD_COUNT( cat_state ); 

This can be useful if you need to declare an array... a-what..? a vector, I swear I meant a vector of values, one for each state.

Fine Print

Now it's time for a small confession: I said earlier that the safe bit fields are a complete drop-in replacement for integer bit fields. Well, this is not entirely true, and there are two cases where the client code has to be modified. One such case is using a bit field as an argument of a function which accepts integers. Now, in many cases you really want such code to be modified, so it's a good thing it no longer compiles as-is. But you may have to deal with functions you cannot modify, for example, printf. You may take this opportunity to rewrite the code in question, and, for example, write a stream insertion operator for your safe bit fields. But if you need printf, you'll have to provide a member function which explicitly converts safe bit fields to integers.

The second case when the client code cannot remain unchanged is when the bit fields were used with unnamed integer constants, for example:

Cat& cat = find_cat(); 
if ( cat.state & 0x0001 ) ... 

Again, in most cases forcing the programmer to rewrite such code is a service to mankind. The program should have used named constants even when they were just macros or const integers, if not for type safety then at least for maintainability. However, many C programmers would not as readily condemn use of literal zero with bit fields:

cat_state state = 0; 

Alas, this will no longer work with safe bit fields: even 0 must be of the right type. That's why Safe_bit_const::make_bit_const() accepts 0 as a valid bit index and returns a bit constant with no bits set, so a "zero state" can be created:

BIT_CONST( cat_state, CAT_NO_STATE, 0 ); 

But the client code must be rewritten like this:

cat_state state = CAT_NO_STATE; 

Or we can take advantage of the default constructor for safe bit fields, which initializes the field to zero:

cat_state state; 

Conclusion

Let's step back and look at what we have just accomplished. First of all, any code which uses integer bit fields and already provides types for them (typedef or enum) even though these types give only "fake" safety, will just recompile with new, safe bit field declarations, and all invalid uses of bit fields will become compile-time errors (other than the two scenarios we just discussed, which were just asking to be rewritten anyway). Second, if the code used bare integers with no type names at all, all declarations of bit field variables will need to change, such as declarations of class members and function arguments. But the code which uses these variables, such as bitwise expressions and logical conditions, will again recompile with no changes and become safe against bit abuse.

We have shown how to convert any code which uses integer words to manipulate individual bits to using safe bit fields, without actually changing any code which does the manipulations. Only the declarations of the bit fields must be changed.

Several kinds of errors commonly found in programs which operate on individual bits are found at compilation time. The obvious error is, of course, mixing bit fields which contain completely different states of completely unrelated objects:

if ( cat_state & DOG_BARKING ) ... 

But that's not all: two more kinds of errors, also quite common, are detected. The first one happens when the programmer forgets the rules of operator precedence:

if ( cat_state & CAT_SLEEPING == 0 ) ... 

while the second one is more of a typo, using logical operators instead of bitwise ones

if ( dog_state && DOG_BARKING ) ... 

The author used safe bit fields to replace integer bit fields in a large commercial software application. About 140,000 lines of code were affected, but only about 100 lines in two header files had to be changed. Four instances of invalid bit use were found, which had escaped detection by testing and debugging.

Listings

Listing 1: Safe bit field class template.

template <unsigned int unique_index, typename word_t> 
class Safe_bit_field 
{

 public:

 // Corresponding constant bit field type.
 typedef Safe_bit_const<unique_index, word_t> const_t;
 friend class Safe_bit_const<unique_index, word_t>; 

// Default constructor, allows to construct bit fields on the stack.
 Safe_bit_field() : word( 0 ) {}

 // Copy constructor and assignment operators.
 Safe_bit_field( const Safe_bit_field& rhs ) : word( rhs.word ) {}
 Safe_bit_field operator=( Safe_bit_field rhs ) { word = rhs.word; return 
     *this; }

 // Copy constructor and assignment operators from constant bit fields.
 Safe_bit_field( const const_t& rhs ) : word( rhs.word ) {}
 Safe_bit_field operator=( const_t rhs ) { word = rhs.word; return *this; }

 // Comparison operators. 
 bool operator==( Safe_bit_field rhs ) const { return word == rhs.word; }
 bool operator!=( Safe_bit_field rhs ) const { return word != rhs.word; }
 bool operator<( Safe_bit_field rhs ) const { return word < rhs.word; }
 bool operator>( Safe_bit_field rhs ) const { return word > rhs.word; }
 bool operator<=( Safe_bit_field rhs ) const { return word <= rhs.word; }
 bool operator>=( Safe_bit_field rhs ) const { return word >= rhs.word; }
 //
 bool operator==( const_t rhs ) const { return word == rhs.word; }
 bool operator!=( const_t rhs ) const { return word != rhs.word; }
 bool operator<( const_t rhs ) const { return word < rhs.word; }
 bool operator>( const_t rhs ) const { return word > rhs.word; }
 bool operator<=( const_t rhs ) const { return word <= rhs.word; }
 bool operator>=( const_t rhs ) const { return word >= rhs.word; }

 // Bitwise operations. 
 Safe_bit_field operator|( Safe_bit_field rhs ) const { return Safe_bit_field( word | rhs.word ); }
 Safe_bit_field operator&( Safe_bit_field rhs ) const { return Safe_bit_field( word & rhs.word ); }
 Safe_bit_field operator^( Safe_bit_field rhs ) const { return Safe_bit_field( word ^ rhs.word ); }
 Safe_bit_field operator~() const { return Safe_bit_field( ~word ); }
 Safe_bit_field operator|=( Safe_bit_field rhs ) { word |= rhs.word; return Safe_bit_field( *this ); }
 Safe_bit_field operator&=( Safe_bit_field rhs ) { word &= rhs.word; return Safe_bit_field( *this ); }
 Safe_bit_field operator^=( Safe_bit_field rhs ) { word ^= rhs.word; return Safe_bit_field( *this ); }
 //
 Safe_bit_field operator|( const_t rhs ) const { return Safe_bit_field( word | rhs.word ); } 
 Safe_bit_field operator&( const_t rhs ) const { return Safe_bit_field( word & rhs.word ); }
 Safe_bit_field operator^( const_t rhs ) const { return Safe_bit_field( word ^ rhs.word ); }
 Safe_bit_field operator|=( const_t rhs ) { word |= rhs.word; return Safe_bit_field( *this ); }
 Safe_bit_field operator&=( const_t rhs ) { word &= rhs.word; return Safe_bit_field( *this ); }
 Safe_bit_field operator^=( const_t rhs ) { word ^= rhs.word; return Safe_bit_field( *this ); }

 // Conversion to bool. 
operator const bool() const { return word; }

 // Shift operators shift bits inside the bit field.
 Safe_bit_field operator<<( unsigned int s ) { return Safe_bit_field( word << s ) ; }
 Safe_bit_field operator>>( unsigned int s ) { return Safe_bit_field( word >> s ) ; }
 Safe_bit_field operator<<=( unsigned int s ) { word <<= s; return *this; }
 Safe_bit_field operator>>=( unsigned int s ) { word >>= s; return *this; }

 // Word size is also the maximum number of different bit fields for a given word type.
 static size_t size() { return sizeof( word_t ); }

 // Type of the bit field is available if needed.
 typedef word_t bit_word_t;

 private:

 // Bits - the real data.
 word_t word;

 // Private constructor from an integer type. Don't put too much stock into
 // explicit declaration, it's better than nothing but 
 // does not solve all problems with undesired conversions because of 
 // direct initialization.
 explicit Safe_bit_field( word_t init ) : word( init ) {}

 // Here comes the interesting stuff: all the operators designed to trap 
 // unintended conversions and make them not compile.
 template <typename T> Safe_bit_field operator|( T ) const 
{ Forbidden_conversion<T> forbidden_conversion; return *this; }
 template <typename T> Safe_bit_field operator&( T ) const 
{ Forbidden_conversion<T> forbidden_conversion; return *this; }
 template <typename T> Safe_bit_field operator^( T ) const 
{ Forbidden_conversion<T> forbidden_conversion; return *this; }
 template <typename T> Safe_bit_field operator|=( T ) const 
{ Forbidden_conversion<T> forbidden_conversion; return *this; }
 template <typename T> Safe_bit_field operator&=( T ) const 
{ Forbidden_conversion<T> forbidden_conversion; return *this; }
 template <typename T> Safe_bit_field operator^=( T ) const 
{ Forbidden_conversion<T> forbidden_conversion; return *this; }
 // 

 template <typename T> bool operator==( const T ) const {
  Forbidden_conversion<T> 
  forbidden_conversion; return true; }
 template <typename T> bool operator!=( const T ) const {
  Forbidden_conversion<T> forbidden_conversion; return true; }
 template <typename T> bool operator<( const T ) const { Forbidden_conversion<T> 
  forbidden_conversion; return true; }
 template <typename T> bool operator>( const T ) const {
  Forbidden_conversion<T> forbidden_conversion; return true; }
 template <typename T> bool operator<=( const T ) const { 
  Forbidden_conversion<T> forbidden_conversion; return true; }
 template <typename T> bool operator>=( const T ) const { 
  Forbidden_conversion<T> forbidden_conversion; return true; } 
}; 

// Non-member template operators to catch errors where safe label is not the first argument. 
template <unsigned int unique_index, typename word_t> 
  inline Safe_bit_field<unique_index, word_t> operator&( bool, 
  Safe_bit_field<unique_index, word_t> rhs ) 
  { Forbidden_conversion<word_t> forbidden_conversion; return rhs; } 
template <unsigned int unique_index, typename word_t> 
  inline Safe_bit_field<unique_index, word_t> operator|( bool, 
  Safe_bit_field<unique_index, word_t> rhs ) 
  { Forbidden_conversion<word_t> forbidden_conversion; return rhs; } 
template <unsigned int unique_index, typename word_t> 
  inline Safe_bit_field<unique_index, word_t> operator^( bool, 
  Safe_bit_field<unique_index, word_t> rhs ) 
  { Forbidden_conversion<word_t> forbidden_conversion; return rhs; } 
template <unsigned int unique_index, typename word_t> 
  inline Safe_bit_field<unique_index, word_t> operator==( bool, 
  Safe_bit_field<unique_index, word_t> rhs ) 
  { Forbidden_conversion<word_t> forbidden_conversion; return rhs; } 
template <unsigned int unique_index, typename word_t> 
  inline Safe_bit_field<unique_index, word_t> operator!=( bool, 
  Safe_bit_field<unique_index, word_t> rhs ) 
  { Forbidden_conversion<word_t> forbidden_conversion; return rhs; } 

Listing 2: Safe bit constant class template.


template <unsigned int unique_index, typename word_t = unsigned long> 
class Safe_bit_const {

 public:

 // Static factory constructor.
 template <unsigned int i> static Safe_bit_const make_bit_const() 
 { 

 CTASSERT( i <= 8*sizeof(word_t) );
 CTASSERT( sizeof(Safe_bit_const) == sizeof(word_t) );
 return Safe_bit_const( ( i > 0 ) ? ( word_t(1) << ( ( i > 0 ) ? ( i - 1 ) : 0 ) ) : 0 );
 }

 // Corresponding bit field type.
 typedef Safe_bit_field<unique_index, word_t> field_t;
 friend class Safe_bit_field<unique_index, word_t>;

 // Copy constructor and assignment operators.
 Safe_bit_const( const Safe_bit_const& rhs ) : word( rhs.word ) {}
 Safe_bit_const operator=( Safe_bit_const rhs ) { word = rhs.word; return *this; }

 // Comparison operators.
 bool operator==( Safe_bit_const rhs ) const { return word == rhs.word; }
 bool operator!=( Safe_bit_const rhs ) const { return word != rhs.word; }
 bool operator<( Safe_bit_const rhs ) const { return word < rhs.word; }
 bool operator>( Safe_bit_const rhs ) const { return word > rhs.word; }
 bool operator<=( Safe_bit_const rhs ) const { return word <= rhs.word; }
 bool operator>=( Safe_bit_const rhs ) const { return word >= rhs.word; }
 //
 bool operator==( field_t rhs ) const { return word == rhs.word; }
 bool operator!=( field_t rhs ) const { return word != rhs.word; }
 bool operator<( field_t rhs ) const { return word < rhs.word; }
 bool operator>( field_t rhs ) const { return word > rhs.word; }
 bool operator<=( field_t rhs ) const { return word <= rhs.word; }
 bool operator>=( field_t rhs ) const { return word >= rhs.word; }

 // Bitwise operations. 
Safe_bit_const operator|( Safe_bit_const rhs ) const { return Safe_bit_const( word | rhs.word ); }
 Safe_bit_const operator&( Safe_bit_const rhs ) const { return Safe_bit_const( word & rhs.word ); }
 Safe_bit_const operator^( Safe_bit_const rhs ) const { return Safe_bit_const( word ^ rhs.word ); }
 Safe_bit_const operator~() const { return Safe_bit_const( ~word ); }
 Safe_bit_const operator|=( Safe_bit_const rhs ) { word |= rhs.word; return Safe_bit_const( *this ); }
 Safe_bit_const operator&=( Safe_bit_const rhs ) { word &= rhs.word; return Safe_bit_const( *this ); }
 Safe_bit_const operator^=( Safe_bit_const rhs ) { word ^= rhs.word; return Safe_bit_const( *this ); }
 // 

 field_t operator|( field_t rhs ) const { return field_t( word | rhs.word ); }
 field_t operator&( field_t rhs ) const { return field_t( word & rhs.word ); }
 field_t operator^( field_t rhs ) const { return field_t( word ^ rhs.word ); }

 // Shift operators shift bits inside the bit field. Does not make sense, most of 
 // the time, except perhaps to loop over bit fields and increment them.
 Safe_bit_const operator<<( unsigned int s ) { return Safe_bit_const( word << s ); }
 Safe_bit_const operator>>( unsigned int s ) { return Safe_bit_const( word >> s ); }
 Safe_bit_const operator<<=( unsigned int s ) { word <<= s; return *this; }
 Safe_bit_const operator>>=( unsigned int s ) { word >>= s; return *this; }

 // Word size is also the maximum number of different bit fields for a given word type.
 static size_t size() { return sizeof( word_t ); }

 // Type of the bit field is available if needed.
 typedef word_t bit_word_t;

 //////////////////////////////////////////////////////////////////////////////// 
//////////////////////////////////////////////////////////
 private:

 // Private constructor from an integer type. 
 explicit Safe_bit_const( word_t init ) : word( init ) {}

 // Bits - the real data.
 word_t word;

 // Here comes the interesting stuff: all the operators designed to trap 
 // unintended conversions and make them not compile.
 // Operators below handle code like this:
 // Safe_bit_const<1> label1;
 // Safe_bit_const<2> label2;
 // if ( label1 & label2 ) { ... }
 //
 // These operators are private, and will not instantiate in any event because of 
 //the incomplete forbidden_conversion struct.
 template <typename T> Safe_bit_const operator|( T ) const 
{ Forbidden_conversion<T> forbidden_conversion; return *this; }
 template <typename T> Safe_bit_const operator&( T ) const 
{ Forbidden_conversion<T> forbidden_conversion; return *this; }
 template <typename T> Safe_bit_const operator^( T ) const 
{ Forbidden_conversion<T> forbidden_conversion; return *this; }
 template <typename T> Safe_bit_const operator|=( T ) const 
{ Forbidden_conversion<T> forbidden_conversion; return *this; }
 template <typename T> Safe_bit_const operator&=( T ) const 
{ Forbidden_conversion<T> forbidden_conversion; return *this; }
 template <typename T> Safe_bit_const operator^=( T ) const 
{ Forbidden_conversion<T> forbidden_conversion; return *this; }

 // And the same thing for comparisons:
 // if ( label1 == label2 ) { ... }
 template <typename T> bool operator==( const T ) const 
{ Forbidden_conversion<T> forbidden_conversion; return true; }
 template <typename T> bool operator!=( const T ) const 
{ Forbidden_conversion<T> forbidden_conversion; return true; }
 template <typename T> bool operator<( const T ) const 
{ Forbidden_conversion<T> forbidden_conversion; return true; }
 template <typename T> bool operator>( const T ) const 
{ Forbidden_conversion<T> forbidden_conversion; return true; }
 template <typename T> bool operator<=( const T ) const 
{ Forbidden_conversion<T> forbidden_conversion; return true; }
 template <typename T> bool operator>=( const T ) const 
{ Forbidden_conversion<T> forbidden_conversion; return true; } 
}; 

Listing 3: Helper macros.

// All macros are conditionally defined to use the Safe_bit_field classes if 
// SAFE_BIT_FIELD is defined. 
#define SAFE_BIT_FIELD 

// Field type declaration. 
#ifdef SAFE_BIT_FIELD 
#define BIT_FIELD( word_t ) typedef Safe_bit_field<__LINE__, word_t> 
#else 
#define BIT_FIELD( word_t ) typedef word_t 
#endif // SAFE_BIT_FIELD 

// Bit constant declaration. 
#ifdef SAFE_BIT_FIELD 
#define BIT_CONST( field_t, label, bit_index ) static const field_t::const_t label = 
field_t::const_t::make_bit_const<bit_index>() 
#else 
inline size_t make_bit_const( size_t i ) { return ( i > 0 ) ? ( size_t(1) << ( ( i > 0 ) ? ( i - 1 ) : 0 ) ) : 0; } 
#define BIT_CONST( field_t, label, bit_index ) static const field_t label = 
make_bit_const( bit_index ) 
#endif // SAFE_BIT_FIELD 

// Complex bit constant declaration. 
#ifdef SAFE_BIT_FIELD 
#define BIT_CONSTS( field_t, label ) static const field_t::const_t label 
#else 
#define BIT_CONSTS( field_t, label ) static const field_t label 
#endif // SAFE_BIT_FIELD 

// Maximum number of bits for a given type: 
#ifdef SAFE_BIT_FIELD 
#define BIT_FIELD_COUNT( field_t ) field_t::size() 
#else 
#define BIT_FIELD_COUNT( field_t ) (8*sizeof(field_t)) 
#endif // SAFE_BIT_FIELD 

References

[1] Stanislav Sykora, Bits and Flags C/C++ Macros
http://www.ebyte.it/library/codesnippets/StansCppMacros_BitsAndFlags.html

[2] Kevlin Henney, FlagWaving
http://www.two-sdg.demon.co.uk/curbralan/papers/FlagWaiving.pdf

[3] Matthew Wilson, Flexible C++ #11: Imperfect enums
http://www.ddj.com/dept/cpp/184403893

[4] Tiny Template Library: working with flags
http://www.codeproject.com/cpp/TTLFlags.asp

[5] BOOST_STRONG_TYPEDEF
http://www.boost.org/libs/serialization/doc/strong_typedef.html

[6] Andrei Alexandrescu, Modern C++ Design, Section 2.1.

[7] Dan Sacks, Catching errors early with compile-time assertions, Embedded Systems Design, June 2006,
http://www.embedded.com/showArticle.jhtml?articleID=164900888

[8] Boost provides BOOST_STATIC_ASSERT macro, see
http://www.boost.org/doc/html/boost_staticassert.html

[9] Ralf Holly, Compile-Sime Assertions, C/C++ Users Journal, November 2004
http://pera-software.com/articles/compile-time-assertions.pdf

[10] Frank Laub
http://boost­consulting.com/vault/index.php?action=downloadfile&filename=enum_rev4.6 .zip

Talk back!

Have an opinion? Readers have already posted 20 comments about this article. Why not add yours?

About the author

-