This is my approach to bit flags:
template<typename E>
class Options {
unsigned long values;
constexpr Options(unsigned long v, int) : values{v} {}
public:
constexpr Options() : values(0) {}
constexpr Options(unsigned n) : values{1UL << n} {}
constexpr bool operator==(Options const& other) const {
return (values & other.values) == other.values;
}
constexpr bool operator!=(Options const& other) const {
return !operator==(other);
}
constexpr Options operator+(Options const& other) const {
return {values | other.values, 0};
}
Options& operator+=(Options const& other) {
values |= other.values;
return *this;
}
Options& operator-=(Options const& other) {
values &= ~other.values;
return *this;
}
};
#define DECLARE_OPTIONS(name) class name##__Tag; using name = Options
#define DEFINE_OPTION(name, option, index) constexpr name option(index)
You can use it like so:
DECLARE_OPTIONS(ENUM);
DEFINE_OPTIONS(ENUM, ONE, 0);
DEFINE_OPTIONS(ENUM, TWO, 1);
DEFINE_OPTIONS(ENUM, THREE, 2);
DEFINE_OPTIONS(ENUM, FOUR, 3);
Then ONE + TWO
is still of type ENUM
. And you can re-use the class to define multiple bit flag sets that are of different, incompatible types.
I personally don't like using |
and &
to set and test bits. It's the logical operation that needs to be done to set and test, but they don't express the meaning of the operation unless you think about bitwise operations. If you read out ONE | TWO
you might think that you want either ONE or TWO, not necessarily both. This is why I prefer using +
to add flags together and ==
to test if a flag is set.
See this blog post for more details on my suggested implementation.