[Interest] Flags: Non-intrusive, type-safe bitwise operators for enums
Hi everyone, I would like to gauge interest in a library I've been working on called Flags, which provides type-safe, non-intrusive bitwise operators for C++ enumerations. The primary goal of the library is to make the usage of flag-like enums safer by preventing accidental mixing of different types while still allowing them to be used with standard bitwise logic. A preliminary version of the library was presented here two years ago. The reception was positive and led to an enlightening discussion with Andrey Semashev, who pointed out several flaws in the design — notably potential ODR violations and the need for argument-dependent lookup (ADL) of operators. An enhanced version was then presented at C++Now 2024, where it was also well-received. After incorporating all feedback and allowing another year for the library to mature within internal projects, I have decided to resubmit it here. Key Features: * Type-safety: Prevents accidental mixing of different enum types and disables built-in arithmetic operators for enabled enums to turn logical errors into compilation errors. * Non-intrusive: Can be opted-in for existing enums (both scoped and unscoped) without modifying their definition. Simple macro-based opt-in via BOOST_FLAGS(...) [currently intended name]. * Compile-Time Friendly: Everything is constexpr, making it usable in template arguments. * Zero Overhead: Designed as zero-cost abstraction in optimized builds. * Distinguishes between flag-sets and their negations at the type level (based on a sound mathematical model). * Requires at least C++11, uses newer features if present Resources: * Documentation: https://tobias-loew.github.io/flags/ * GitHub Repository: https://github.com/tobias-loew/flags * Boost-Mail-Archive first presentation: https://lists.boost.org/archives/list/boost@lists.boost.org/thread/EZXQFJWA5... * C++Now 2024 Presentation: "Fun with Flags", https://www.youtube.com/watch?v=2dzWasWblRc * Godbolt Example: https://godbolt.org/z/PPoKaGrzc I am interested in hearing if the community sees a place for this in Boost or has any feedback on the design. Best regards, Tobias Loew
On Mon, Mar 16, 2026 at 12:00 PM Tobias Loew via Boost <boost@lists.boost.org> wrote:
I would like to gauge interest in a library [...] which provides type-safe, non-intrusive bitwise operators for C++ enumerations. [...] I am interested in hearing if the community sees a place for this in Boost
FWIW, I rolled-up my own a while back, like many I'm sure, so there's obviously a need, IMHO. I'd welcome a Boost quality one, which would be, I'm sure, heads and shoulders better than mine. --DD // Adapted (and fixed) from // https://softwareengineering.stackexchange.com/a/338472/189774 template <typename Enum> class BitFlags { static_assert(std::is_enum_v<Enum>); using underlying_t = typename std::underlying_type<Enum>::type; constexpr const static int number_of_bits = std::numeric_limits<underlying_t>::digits; public: ... private: std::bitset<number_of_bits> bits_; }; template <typename Enum> constexpr std::enable_if_t<std::is_enum_v<Enum>, BitFlags<Enum>> operator|(Enum left, Enum right) { return BitFlags<Enum>(left) | right; } template <typename Enum> constexpr std::enable_if_t<std::is_enum_v<Enum>, BitFlags<Enum>> operator&(Enum left, Enum right) { return BitFlags<Enum>(left) & right; } template <typename Enum> constexpr std::enable_if_t<std::is_enum_v<Enum>, BitFlags<Enum>> operator^(Enum left, Enum right) { return BitFlags<Enum>(left) ^ right; }
Thanks for the comment. The code at https://softwareengineering.stackexchange.com/a/338472/189774 is an interesting example of a flag implementation, as it also maps enum values to bit positions. However, while it claims to offer type safety, it only provides bitwise operator overloads for the given enum. This seems fine since the features work, but there is a problem: erroneous code still compiles. For example, when two different enums are accidentally mixed: enum e {e0, e1, e2, e3}; enum f {f0, f1, f2, f3}; if ((e0 | e3) & e1) {...} // uses bitflag class -> evaluates to false if ((e0 | f3) & e1) {...} // use integral promotion and built-in operators -> evaluates to true Of course, this problem only exists for unscoped enums, but flag-like enums are usually defined that way. My Flags library dedicates a lot of code to prohibiting operations that should not compile; for an enabled enum, only bitwise operators with itself or its complement type are allowed.
On 3/16/26 11:59, Tobias Loew via Boost wrote:
Key Features:
* Type-safety: Prevents accidental mixing of different enum types and disables built-in arithmetic operators for enabled enums to turn logical errors into compilation errors. * Non-intrusive: Can be opted-in for existing enums (both scoped and unscoped) without modifying their definition. Simple macro-based opt-in via BOOST_FLAGS(...) [currently intended name]. * Compile-Time Friendly: Everything is constexpr, making it usable in template arguments. * Zero Overhead: Designed as zero-cost abstraction in optimized builds. * Distinguishes between flag-sets and their negations at the type level (based on a sound mathematical model). * Requires at least C++11, uses newer features if present
While better support for bit flags based on scoped enums is undoubtedly useful, I don't like how verbose the example code is. I've considered an alternate solution to the same problem based on a wrapper around std::bitset. template<typename Enum> class enum_bitset { public: auto operator[](Enum idx) { return this->data_store[static_cast<std::size_t>(idx)]; } // ...and all of the other member functions... private: std::bitset<...> data_store; }; This has the advantage of getting rid of all of the ugly boost::flags::nth_bits and boost::flags::anys in the example code: enum class pizza_topping { tomato, cheese, salami, olives, garlic }; void order_pizza(enum_bitset<pizza_topping> const &toppings) { std::cout << "Pizza ordered with\n"; if (toppings[pizza_toppings::tomato]) { std::cout << "- tomato\n"; } if (toppings[pizza_toppings::cheese]) { std::cout << "- cheese\n"; } if (toppings[pizza_toppings::salami]) { std::cout << "- salami\n"; } if (toppings[pizza_toppings::olives]) { std::cout << "- olives\n"; } if (toppings[pizza_toppings::garlic]) { std::cout << "- garlic\n"; } std::cout << "\n"; } Isn't that so much more readable and compact? -- Rainer Deyke - rainerd@eldwood.com
Le mardi 17 mars 2026 à 05:25 +0100, Rainer Deyke via Boost a écrit :
On 3/16/26 11:59, Tobias Loew via Boost wrote:
While better support for bit flags based on scoped enums is undoubtedly useful, I don't like how verbose the example code is.
I've considered an alternate solution to the same problem based on a wrapper around std::bitset.
template<typename Enum> class enum_bitset { public: auto operator[](Enum idx) { return this->data_store[static_cast<std::size_t>(idx)]; } // ...and all of the other member functions... private: std::bitset<...> data_store; };
This has the advantage of getting rid of all of the ugly boost::flags::nth_bits and boost::flags::anys in the example code:
enum class pizza_topping { tomato, cheese, salami, olives, garlic };
void order_pizza(enum_bitset<pizza_topping> const &toppings) { std::cout << "Pizza ordered with\n"; if (toppings[pizza_toppings::tomato]) { std::cout << "- tomato\n"; } if (toppings[pizza_toppings::cheese]) { std::cout << "- cheese\n"; } if (toppings[pizza_toppings::salami]) { std::cout << "- salami\n"; } if (toppings[pizza_toppings::olives]) { std::cout << "- olives\n"; } if (toppings[pizza_toppings::garlic]) { std::cout << "- garlic\n"; } std::cout << "\n"; }
Isn't that so much more readable and compact?
Much more. This is one of the reason i included bitset support in my indexed_array library ( https://github.com/Julien-Blanc-tgcm/indexed_array ), the bitset doing exactly that : access individual bits via an enum value. I've come to the conclusion that any representation of enum flags with C++ enums is a design mistake by itself (a pretty widespread one, but still a mistake). Enums are individual values, enum flags are a set of values, they are different by nature. In C++, different things deserve a different type, call it an enum_bitset, an enum_set or whatever, but donc make it a plain enum / enum class. Regards, Julien
On Tue, 17 Mar 2026 at 06:38, Julien Blanc via Boost <boost@lists.boost.org> wrote:
I've come to the conclusion that any representation of enum flags with C++ enums is a design mistake by itself (a pretty widespread one, but still a mistake). Enums are individual values, enum flags are a set of values, they are different by nature. In C++, different things deserve a different type, call it an enum_bitset, an enum_set or whatever, but donc make it a plain enum / enum class.
I totally agree. We’ve had this same discussion in November and I’ve made the same argument. Conceptually, enum values and sets of enum values should be different things. C code has always made the distinction between enums (`enum`) and enum sets (`unsigned int`). The only hurdles here are practical in nature. C++ technically treats any bit pattern of an enum type as a valid value, so you could argue there is no need for a second type. Furthermore, there is no standard “enum set” type, so you have to invent one yourself, which is kind of a big deal for something so fundamental, especially in public interfaces. -Janko
I agree that the presented alternatives are shorter and cleaner, but the aim of the library is not to provide new or better syntax for of flag-like types. Its main purpose is to strengthen existing code by turning logical errors, like - using unrelated enums with bitwise operators - wrong usage of negated flags (negative-bitmasks) - accidental usage of operator && instead of operator & into compilation errors. The library does this with minimal intervention: the definition of the enums is not changed, which implies that overload-resolution, template-instantiations, APIs, ... are not affected. The additional used "boost::flags::" functions from the example are not required. (Only scoped eums in Boolean contexts require an additional operator or function invocation)
В письме от вторник, 17 марта 2026 г. 08:36:05 MSK пользователь Julien Blanc via Boost написал:
I've come to the conclusion that any representation of enum flags with C++ enums is a design mistake by itself (a pretty widespread one, but still a mistake). Enums are individual values, enum flags are a set of values, they are different by nature. In C++, different things deserve a different type, call it an enum_bitset, an enum_set or whatever, but donc make it a plain enum / enum class.
I had the same thoughts when I wrote my take on this (it's in the list of "Other flags-like implementations" in this proposed library's docs). So, naturally I agree. And yes, having a dedicated type makes usage much more ergonomic. But on the other hand, a lot of people consider having a dedicated type for flags/bitmasks a completely unnecessary overhead. I think these 2 groups will not find an agreement.
Am 17.03.26 um 05:25 schrieb Rainer Deyke via Boost:
I've considered an alternate solution to the same problem based on a wrapper around std::bitset.
template<typename Enum> class enum_bitset { public: auto operator[](Enum idx) { return this->data_store[static_cast<std::size_t>(idx)]; } // ...and all of the other member functions... private: std::bitset<...> data_store; };
This has the advantage of getting rid of all of the ugly boost::flags::nth_bits and boost::flags::anys in the example code: I was also thinking about that: Having to initialize an enum with powers-of-2 is a pitfall, although very common for flags. Maybe you already have flags.
Imagine writing a C++ wrapper around the Windows flags posted in the example. There being able to reuse the actual values can be very useful: enum class BS { Notify = BS_NOTIFY, Foo = BS_FOO, Bar = BS_Bar }; But for greenfield implementations I like not having the boilerplate of having to initialize enumerators. On the other hand it makes them stick out as flags. FWIW: Both approaches seem to get optimized to literally the same code. Random example: https://godbolt.org/z/Yjea7qzfv What I am missing though are predefined bitsets: With the pizza example you can have `Topping::Cheese` in an enum class but no good way to have `Topping::All` or `Topping::Vegetarian`, e.g. for `bool isVegan(... toppings) { return toppings & ~Topping::NonVegan; } Of course you can define such as (constexpr) constants, but not anymore inside the bitset class, i.e. Topping, but only on the namespace as "AllToppings" or subclassing. I have also used bits and bitsets in switch-case constructs, bit std::bitset::to_(u)ulong is only constexpr in C++23 So I had use for both variants. Maybe they can be consolidated: template<typename Enum, bool isDense=true> class enum_bitset I see use for that in Boost as it seems to be common enough to avoid having to reimplement it Best, Alex
participants (7)
-
Alexander Grund -
Dmitry Arkhipov -
Dominique Devienne -
Janko Dedic -
Julien Blanc -
Rainer Deyke -
tobi@die-loews.de