Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Type safe(r) bitflags in C++?

Tags:

While revising some old c++ code, I ran across several bitflags defined as enums.

enum FooFlags {     FooFlag1 = 1 << 0,     FooFlag2 = 1 << 1,     FooFlag3 = 1 << 2     // etc... }; 

This isn't uncommon, but it bothered me that as soon as you start to combine flags, you lose the type information.

int flags = FooFlag1 | FooFlag2;   // We've lost the information that this is a set of flags relating to *Foo* 

Some searching on SO showed that I'm not the only one bothered by this.

One alternative is to declare flags as #defines or const integrals, so bitwise operations wouldn't transform the type (probably). The problem with this is it allows our bit set to commingle with unrelated flags, via ints or other enums.

I'm familiar with std::bitset and boost::dynamic_bitset, but neither are designed to address my issue. What I'm looking for is something like C#'s FlagsAttribute.

My question is, what other solutions are there for a (more) type safe set of bitflags?

I'll post my own solution below.

like image 788
luke Avatar asked Nov 19 '10 16:11

luke


2 Answers

You can overload operators for enumeration types that return the proper typed result.

inline FooFlags operator|(FooFlags a, FooFlags b) {   return static_cast<FooFlags>(+a | +b); } 

It should be noted that to be theoretically safe, you should declare manually the highest possible value so the enumeration type's range is guaranteed to catch all the combinations.

  • Actually that is not needed: An enumeration's range will always be able to catch all combination, because the highest positive value of an enumeration's range is always (2^N)-1 for the first N being able to represent the highest enumerator. That value has all bits 1.
like image 79
Johannes Schaub - litb Avatar answered Sep 19 '22 13:09

Johannes Schaub - litb


Here's my own solution, using elements of c++0x that the current version of VS2010 allows for:

#include <iostream> #include <numeric> #include <string>  #include <initializer_list>  template <typename enumT> class FlagSet {     public:          typedef enumT                     enum_type;         typedef decltype(enumT()|enumT()) store_type;          // Default constructor (all 0s)         FlagSet() : FlagSet(store_type(0))         {          }          // Initializer list constructor         FlagSet(const std::initializer_list<enum_type>& initList)         {             // This line didn't work in the initializer list like I thought it would.  It seems to dislike the use of the lambda.  Forbidden, or a compiler bug?             flags_ = std::accumulate(initList.begin(), initList.end(), store_type(0), [](enum_type x, enum_type y) { return x | y; })         }          // Value constructor         explicit FlagSet(store_type value) : flags_(value)         {          }          // Explicit conversion operator         operator store_type() const         {             return flags_;         }          operator std::string() const         {             return to_string();         }          bool operator [] (enum_type flag) const         {             return test(flag);         }          std::string to_string() const         {             std::string str(size(), '0');              for(size_t x = 0; x < size(); ++x)             {                 str[size()-x-1] = (flags_ & (1<<x) ? '1' : '0');             }              return str;         }          FlagSet& set()         {             flags_ = ~store_type(0);             return *this;         }          FlagSet& set(enum_type flag, bool val = true)         {             flags_ = (val ? (flags_|flag) : (flags_&~flag));             return *this;         }          FlagSet& reset()         {             flags_ = store_type(0);             return *this;         }          FlagSet& reset(enum_type flag)         {             flags_ &= ~flag;             return *this;         }          FlagSet& flip()         {             flags_ = ~flags_;             return *this;         }          FlagSet& flip(enum_type flag)         {             flags_ ^= flag;             return *this;         }          size_t count() const         {             // http://www-graphics.stanford.edu/~seander/bithacks.html#CountBitsSetKernighan              store_type bits = flags_;             size_t total = 0;             for (; bits != 0; ++total)             {                 bits &= bits - 1; // clear the least significant bit set             }             return total;         }          /*constexpr*/ size_t size() const   // constexpr not supported in vs2010 yet         {             return sizeof(enum_type)*8;         }          bool test(enum_type flag) const         {             return (flags_ & flag) > 0;         }          bool any() const         {             return flags_ > 0;         }          bool none() const         {             return flags == 0;         }      private:          store_type flags_;  };  template<typename enumT> FlagSet<enumT> operator & (const FlagSet<enumT>& lhs, const FlagSet<enumT>& rhs) {     return FlagSet<enumT>(FlagSet<enumT>::store_type(lhs) & FlagSet<enumT>::store_type(rhs)); }  template<typename enumT> FlagSet<enumT> operator | (const FlagSet<enumT>& lhs, const FlagSet<enumT>& rhs) {     return FlagSet<enumT>(FlagSet<enumT>::store_type(lhs) | FlagSet<enumT>::store_type(rhs)); }  template<typename enumT> FlagSet<enumT> operator ^ (const FlagSet<enumT>& lhs, const FlagSet<enumT>& rhs) {     return FlagSet<enumT>(FlagSet<enumT>::store_type(lhs) ^ FlagSet<enumT>::store_type(rhs)); }  template <class charT, class traits, typename enumT> std::basic_ostream<charT, traits> & operator << (std::basic_ostream<charT, traits>& os, const FlagSet<enumT>& flagSet) {     return os << flagSet.to_string(); } 

The interface is modeled after std::bitset. My aim was to be true to the c++ ethos of type safety and minimal (if any) overhead. I'd welcome any feedback on my implementation.

Here's a minimal example:

#include <iostream>  enum KeyMod {     Alt     = 1 << 0,  // 1     Shift   = 1 << 1,  // 2     Control = 1 << 2   // 4 };  void printState(const FlagSet<KeyMod>& keyMods) {     std::cout << "Alt is "     << (keyMods.test(Alt)     ? "set" : "unset") << ".\n";     std::cout << "Shift is "   << (keyMods.test(Shift)   ? "set" : "unset") << ".\n";     std::cout << "Control is " << (keyMods.test(Control) ? "set" : "unset") << ".\n"; }  int main(int argc, char* argv[]) {     FlagSet<KeyMod> keyMods(Shift | Control);      printState(keyMods);      keyMods.set(Alt);     //keyMods.set(24);    // error - an int is not a KeyMod value     keyMods.set(Shift);     keyMods.flip(Control);      printState(keyMods);      return 0; } 
like image 26
luke Avatar answered Sep 19 '22 13:09

luke