Andre's Blog
Perfection is when there is nothing left to take away
Waiving a type-safe flag

Sometimes C++ enum's are used to define bit flags, such as read/write attributes, which can provide better type checking at compile time. However, when performing bitwise operations on enum values, one has to cast resulting int's back to enum's, which bypasses strict type checking and makes enum definitions somewhat pointless.

That is, given this enum definition

enum flags_t {
   fRead   = 0x01,
   fWrite  = 0x02,
   fExec   = 0x04,
   fAllFlags = fRead | fWrite | fExec
};

, the code below will produce a compiler error because both enum values are implicitly promoted to integers, so a bitwise OR operation can be performed, and an integer cannot be assigned to an enum value without a cast.

flags_t flags = fRead | fWrite;   // compiler error

One way to work around this problem is to overload the bitwise OR operator as a non-member function for flags_t, as shown below. Note that each of the values has to be cast to an unsigned integer to avoid recursion.

inline flags_t operator | (flags_t f1, flags_t f2) 
{
   return (flags_t) ((unsigned int) f1 | (unsigned int) f2);
}

The remaining overloads are self-explanatory. Note, however, that the result of applying the one's complement operator (~) to flags has to be AND'ed with all flags to ensure that only valid flag bits are set.

   
inline flags_t& operator |= (flags_t& f1, flags_t f2) 
{
   return f1 = f1 | f2;
}   

inline flags_t operator & (flags_t f1, flags_t f2) 
{
   return (flags_t) ((unsigned int) f1 & (unsigned int) f2);
}   

inline flags_t& operator &= (flags_t& f1, flags_t f2) 
{
   return f1 = f1 & f2;
}

inline flags_t operator ~ (flags_t flags) 
{
   return (flags_t) (~((unsigned int) flags) & 
                       (unsigned int) fAllFlags);
}

One might ask if using overloaded functions for bitwise operations would affect code performance. In most cases, optimizing compilers will eliminate function calls altogether, leaving just numeric assignments in place. That is, given this source:

flags_t flags = fRead | fWrite;
printf("%d\n", flags);

, the compiler will generate code as if number 3 was directly passed into printf:

printf("%d\n", 3);

However, if performance is a concern, it makes sense to double-check generated code in performance-critical components.

Comments:
Posted Tue Mar 3 04:43:13 EST 2009 by Diederik

Interesting approach. :)

You may also want to look how Qt 4 addresses this. They've added a template class, and some macro wrappers to have a convenient API while keeping type-safety.

Greetings and thanks for your work on webalizer,

Diederik

Name:

Comment: