The bit flag or bit mask is a recurring topic in the C++ community. Since enum class
has been added, the attempt to create a more type-safe and/or easier-to-use bit flag has never ended. It never succeeded either, as all solutions were flawed in some ways that they've never beaten the straightforward enum
. Some define bitwise-operators for enum class
but use additional macros or specializations, while still requiring manually defined power-of-2 values. Some others create a wrapper class, but lost the capability to express the flag with combination of enum
literals. With several features coming to C++20, I'd like to explore another possible design.
struct AnimationFlag
{
bool FullMatch : 1 = false; //initialization is optional here
bool PlayForever : 1 = false;
bool NonLooping : 1 = false;
};
// usage
AnimationFlag f { .FullMatch = true, .NonLooping = true };
From memory layout point of view, a flag value is rather a (mis)interpretation of an offset in the bit field. Even though bit field always existed in C++, now the difference is that with Designated initializer, we can actually define it with a single, clean statement. In the example above, .FullMatch
and .NonLooping
are initialized to 1
whereas all others to 0
. If you want to absolutely make sure value is always 0-initialized, Bit field member in-class initialization is helpful albeit more verbose.
Now, is it good enough for production? Considering several use cases -
- Composing a mask from both positive and negative flags:
f = a | b
orf = all & ~c
- Determining if a flag is on or off:
(f & a) != 0
- Manipulating flags:
f |= (a | b)
orf ^= c
In our case, determining or manipulating a single flag is trivial. However, it's still missing the expression support - combining flags with bitwise operators (preferably 0-cost and supports constexpr
) is an important part of the design. We can overload operator |, &, ^
for a generic type T
and only allow "our types" by using SFINAE, but it's a really dangerous decision due to the potential conflict with other generic overloads in 3rd-party libraries, and the huge impact on compilation time.
In C++20 we are finally going to have Concepts, although at this point it's unclear how much build time advantage it provides.
//credit https://stackoverflow.com/questions/39768517/structured-bindings-width
struct filler { template< typename type > operator type (); };
template< typename aggregate, typename index_sequence = std::index_sequence<>, typename = void >
struct aggregate_arity
: index_sequence
{
};
template< typename aggregate, std::size_t ...indices >
struct aggregate_arity< aggregate, std::index_sequence< indices... >, std::void_t< decltype(aggregate{(indices, std::declval< filler >())..., std::declval< filler >()}) > >
: aggregate_arity< aggregate, std::index_sequence< indices..., sizeof...(indices) > >
{
};
template< typename aggregate >
constexpr std::size_t get_aggregate_arity()
{
return aggregate_arity< std::remove_reference_t< std::remove_cv_t< aggregate > > >::size();
}
template<typename T>
struct always_false : std::false_type {};
struct flag{};
template<typename T> requires std::is_base_of_v<flag, T>
constexpr T operator& (T a, T b)
{
constexpr std::size_t arity = get_aggregate_arity<T>();
if constexpr (arity == 2/*empty base takes 1*/)
{
auto [a1] = a;
auto [b1] = b;
return T {{}, a1&b1};
}
else if constexpr (arity == 3)
{
auto [a1,a2] = a;
auto [b1,b2] = b;
return T {{}, !!(a1&b1), !!(a2&b2)};
}
else if constexpr (arity == 4)
{
auto [a1,a2,a3] = a;
auto [b1,b2,b3] = b;
return T {{}, !!(a1&b1), !!(a2&b2), !!(a3&b3)};
}
else if constexpr (arity == 5)
{
auto [a1,a2,a3,a4] = a;
auto [b1,b2,b3,b4] = b;
return T {{}, !!(a1&b1), !!(a2&b2), !!(a3&b3), !!(a4&b4)};
}
//all the way up to 32 or 64 ...
else
{
static_assert(always_false<T>::value, "not supported");
return T{};
}
}
In order to support arbitrary length and constexpr
for the bit flag, we can't reinterpret_cast
the underlying memory or use type punning (UB!). That's where structured binding kicks in - although the implementation is tedious due to the lack of variadic structured binding.
struct AnimationFlag : flag
{
bool FullMatch : 1; // = 0 is optional
bool PlayForever : 1;
bool NonLooping : 1;
};
constexpr AnimationFlag f = {.FullMatch = true, .PlayForever = true};
constexpr AnimationFlag f2 = f & ~AnimationFlag{.FullMatch = true};
static_assert(!f2.FullMatch && f2.PlayForever && !f2.NonLooping);
I'd say this is equivalent or better than macro / const / enum based bit flags because
- It doesn't require defining and maintaining power-of-two values
- It's type safe
- It supports any number of flags
What do you think?