State of the art regarding bitmasks
Hi all, I've recently found myself frustrated due to the amount of boilerplate that bitmask types require. Say, for example, asio::cancellation_type_t: https://www.boost.org/doc/libs/latest/boost/asio/cancellation_type.hpp When using enum class to avoid pouring identifiers into the parent namespace, you need to re-implement all the bitwise operators. This is not a lot of work, but is empty boilerplate, and implies adding tests and docs. Reflection may aid with this. I've written a small proof of concept: https://access_refl.compiler-explorer.com/z/jdaxf9q54 Pasting here the basics, just in case: namespace bitmasks { struct bitmask_t {}; inline constexpr bitmask_t bitmask {}; // Checks that t has our annotation consteval bool has_bitmask_annotation(std::meta::info t) { ... } template <class E> concept BitmaskType = std::is_scoped_enum_v<E> && has_bitmask_annotation(^^E); } // Intentionally in the global namespace template <bitmasks::BitmaskType E> E operator|(E e1, E e2) { using U = std::underlying_type_t<E>; return static_cast<E>(static_cast<U>(e1) | static_cast<U>(e2)); } The idea is that you use the [[=bitmask]] annotation on your enum, and you get all the operators for free. I could also add optional pretty-printing. Questions: * Do you think this is a problem worth solving, or am I being too picky? * Do we already have something similar in Boost? I've found a BOOST_BITMASK macro (https://www.boost.org/doc/libs/latest/boost/detail/bitmask.hpp), but it doesn't seem to be public. * Do you think the approach shown above is sound? In particular, operators are in the global namespace so that they work with any enum in any namespace correctly annotated. * Do you think such a small component could be useful in Boost at some point? Thanks, Ruben.
Ruben Perez wrote:
* Do you think the approach shown above is sound? In particular, operators are in the global namespace so that they work with any enum in any namespace correctly annotated.
No, I don't think this works. The operators have to be in the enum namespace. In Describe, I put the operators in namespace boost::describe::operators, and the user then needs to put using boost::describe::operators::operator==; in every namespace where types are defined.
On Tue, 18 Nov 2025 at 11:15, Peter Dimov via Boost <boost@lists.boost.org> wrote:
Ruben Perez wrote:
* Do you think the approach shown above is sound? In particular, operators are in the global namespace so that they work with any enum in any namespace correctly annotated.
No, I don't think this works. The operators have to be in the enum namespace.
Why? Is it because else there wouldn't be any way for the user to opt out from the default operator== definition? I understand that's the case for Describe, but users are explicitly opting into operators with the annotation. Or is there anything else I'm missing?
In Describe, I put the operators in namespace boost::describe::operators, and the user then needs to put
using boost::describe::operators::operator==;
That was my second option, but there are a bunch of them and it ends up being verbose.
in every namespace where types are defined.
_______________________________________________ Boost mailing list -- boost@lists.boost.org To unsubscribe send an email to boost-leave@lists.boost.org https://lists.boost.org/mailman3/lists/boost.lists.boost.org/ Archived at: https://lists.boost.org/archives/list/boost@lists.boost.org/message/6XPRNZTO...
Ruben Perez wrote:
On Tue, 18 Nov 2025 at 11:15, Peter Dimov via Boost <boost@lists.boost.org <mailto:boost@lists.boost.org> > wrote:
Ruben Perez wrote:
* Do you think the approach shown above is sound? In particular, operators are in the global namespace so that they work with any enum in any namespace correctly annotated.
No, I don't think this works. The operators have to be in the enum namespace.
Why? Is it because else there wouldn't be any way for the user to opt out from the default operator== definition? I understand that's the case for Describe, but users are explicitly opting into operators with the annotation. Or is there anything else I'm missing?
On Tue, 18 Nov 2025 at 11:38, Peter Dimov <pdimov@gmail.com> wrote:
Ruben Perez wrote:
On Tue, 18 Nov 2025 at 11:15, Peter Dimov via Boost < boost@lists.boost.org <mailto:boost@lists.boost.org> > wrote:
Ruben Perez wrote: > * Do you think the approach shown above is sound? In particular, > operators are in the global namespace so that they work with any enum > in any namespace correctly annotated.
No, I don't think this works. The operators have to be in the enum namespace.
Why? Is it because else there wouldn't be any way for the user to opt out from the default operator== definition? I understand that's the case for Describe, but users are explicitly opting into operators with the annotation. Or is there anything else I'm missing?
I see now. Thanks. Then the user would need a bunch of using bitmasks::operatorXY; statements in their namespace. This could be made slightly less verbose with a macro, but then I don't see much value in this reflection approach (against Andrey's macro suggestion, for instance).
On 18 Nov 2025 12:55, Ruben Perez via Boost wrote:
Hi all,
I've recently found myself frustrated due to the amount of boilerplate that bitmask types require. Say, for example, asio::cancellation_type_t: https://www.boost.org/doc/libs/latest/boost/asio/cancellation_type.hpp
When using enum class to avoid pouring identifiers into the parent namespace, you need to re-implement all the bitwise operators. This is not a lot of work, but is empty boilerplate, and implies adding tests and docs.
* Do you think this is a problem worth solving, or am I being too picky?
Yes, I've found myself solving the same problem multiple times. I've written a macro-based solution for that in my project.
* Do we already have something similar in Boost? I've found a BOOST_BITMASK macro (https://www.boost.org/doc/libs/latest/boost/detail/bitmask.hpp), but it doesn't seem to be public.
I intended to propose a more lightweight version of this macro (without the deprecated bitmask_set) to Boost.Core, but never got around to do it. I think, we should do it.
* Do you think the approach shown above is sound? In particular, operators are in the global namespace so that they work with any enum in any namespace correctly annotated. * Do you think such a small component could be useful in Boost at some point?
I'm not experienced with C++ reflection, so I can't comment on the implementation. But requiring C++23 for such a basic and widely needed feature seems like a high bar. In particular, I would not be able to use a C++23 library in my code. I would prefer a simpler solution that is compatible with C++17 at the most, although C++11 should be already enough to implement it.
* Do you think this is a problem worth solving, or am I being too picky?
Yes, I've found myself solving the same problem multiple times. I've written a macro-based solution for that in my project.
Great. I could probably use this in Boost.Redis at some point.
* Do we already have something similar in Boost? I've found a BOOST_BITMASK macro (https://www.boost.org/doc/libs/latest/boost/detail/bitmask.hpp), but it doesn't seem to be public.
I intended to propose a more lightweight version of this macro (without the deprecated bitmask_set) to Boost.Core, but never got around to do it. I think, we should do it.
* Do you think the approach shown above is sound? In particular, operators are in the global namespace so that they work with any enum in any namespace correctly annotated. * Do you think such a small component could be useful in Boost at some point?
I'm not experienced with C++ reflection, so I can't comment on the implementation. But requiring C++23 for such a basic and widely needed feature seems like a high bar. In particular, I would not be able to use a C++23 library in my code. I would prefer a simpler solution that is compatible with C++17 at the most, although C++11 should be already enough to implement it.
With the latest comments from Peter, I don't think it does make sense, either. It was a nice proof of concept to play with it, but the requirements are not worth it. The macro approach seems best.
_______________________________________________ Boost mailing list -- boost@lists.boost.org To unsubscribe send an email to boost-leave@lists.boost.org https://lists.boost.org/mailman3/lists/boost.lists.boost.org/ Archived at: https://lists.boost.org/archives/list/boost@lists.boost.org/message/JXKO5BHG...
On Tue, Nov 18, 2025, 10:56 Ruben Perez via Boost <boost@lists.boost.org> wrote:
Hi all,
I've recently found myself frustrated due to the amount of boilerplate that bitmask types require. Say, for example, asio::cancellation_type_t: https://www.boost.org/doc/libs/latest/boost/asio/cancellation_type.hpp
When using enum class to avoid pouring identifiers into the parent namespace, you need to re-implement all the bitwise operators. This is not a lot of work, but is empty boilerplate, and implies adding tests and docs.
Reflection may aid with this. I've written a small proof of concept: https://access_refl.compiler-explorer.com/z/jdaxf9q54
Pasting here the basics, just in case:
namespace bitmasks {
struct bitmask_t {}; inline constexpr bitmask_t bitmask {};
// Checks that t has our annotation consteval bool has_bitmask_annotation(std::meta::info t) { ... }
template <class E> concept BitmaskType = std::is_scoped_enum_v<E> && has_bitmask_annotation(^^E);
}
// Intentionally in the global namespace template <bitmasks::BitmaskType E> E operator|(E e1, E e2) { using U = std::underlying_type_t<E>; return static_cast<E>(static_cast<U>(e1) | static_cast<U>(e2)); }
The idea is that you use the [[=bitmask]] annotation on your enum, and you get all the operators for free. I could also add optional pretty-printing.
Questions:
* Do you think this is a problem worth solving, or am I being too picky? * Do we already have something similar in Boost? I've found a BOOST_BITMASK macro (https://www.boost.org/doc/libs/latest/boost/detail/bitmask.hpp), but it doesn't seem to be public. * Do you think the approach shown above is sound? In particular, operators are in the global namespace so that they work with any enum in any namespace correctly annotated. * Do you think such a small component could be useful in Boost at some point?
Thanks, Ruben. _______________________________________________ Boost mailing list -- boost@lists.boost.org To unsubscribe send an email to boost-leave@lists.boost.org https://lists.boost.org/mailman3/lists/boost.lists.boost.org/ Archived at: https://lists.boost.org/archives/list/boost@lists.boost.org/message/XES3FGMG...
I have always considered this a self-inflicted problem. Saying that an `enum class E { A=1, B=2, C=4 };` can have a value of `E{(int)E::A | (int)E::B}` is a category error. Of course, an object of type E _can_ have this representation, but `enum` is meant to express a full set of valid values. This helps readers understand code and compilers warn about unhandled enum values. Bit masks have been done effortlessly in C since forever. Just use a normal `enum E { A=1, B=2, C=4 };` and create flag sets with `unsigned flags = A | B;`. Trying to shoehorn this into enum classes with macros and reflection (!) is absurd to me, to be honest. If you really want a more modern solution that also makes sense in the type system, I have used a utility class template like this before. ``` enum class E { A, B, C }; flag_set<E> flags{E::A, E::B}; flags.add(E::C); flags.erase(E::A); bool b = flags.contains(E::B); ``` `flag_set<E>` can internally shift enum values and apply them to the underlying integer. Best regards, Janko
On 18 Nov 2025 13:46, Janko Dedic via Boost wrote:
I have always considered this a self-inflicted problem. Saying that an `enum class E { A=1, B=2, C=4 };` can have a value of `E{(int)E::A | (int)E::B}` is a category error. Of course, an object of type E _can_ have this representation, but `enum` is meant to express a full set of valid values. This helps readers understand code and compilers warn about unhandled enum values.
Exhaustive enums are just one use case, bitmasks are another. Enum is just an integer with a given set of predefined values, and using enums to form bitmasks is straightforward and has been done in the wild for ages. A utility that helps this use case would be useful, IMO.
Bit masks have been done effortlessly in C since forever. Just use a normal `enum E { A=1, B=2, C=4 };` and create flag sets with `unsigned flags = A | B;`.
Unscoped enums have known issues, like being implicitly convertible to ints, having no fixed underlying type by default and pouring enumerator names into the enclosing namespace. Enum classes address those issues.
If you really want a more modern solution that also makes sense in the type system, I have used a utility class template like this before.
``` enum class E { A, B, C }; flag_set<E> flags{E::A, E::B}; flags.add(E::C); flags.erase(E::A); bool b = flags.contains(E::B); ```
`flag_set<E>` can internally shift enum values and apply them to the underlying integer.
Why would you want that instead of the natural syntax with bitwise operators?
В письме от вторник, 18 ноября 2025 г. 13:46:22 MSK пользователь Janko Dedic via Boost написал:
enum class E { A, B, C }; flag_set<E> flags{E::A, E::B}; flags.add(E::C); flags.erase(E::A); bool b = flags.contains(E::B);
There was recently (last year?) a library author seeking for endorsements for a library like that. To my knowledge, he didn't get a single one. Seems to me that people in general think that wrapping the bitmask with a struct is too much.
Le 2025-11-18 12:08, Dmitry Arkhipov via Boost a écrit :
В письме от вторник, 18 ноября 2025 г. 13:46:22 MSK пользователь Janko Dedic via Boost написал:
enum class E { A, B, C }; flag_set<E> flags{E::A, E::B}; flags.add(E::C); flags.erase(E::A); bool b = flags.contains(E::B);
There was recently (last year?) a library author seeking for endorsements for a library like that. To my knowledge, he didn't get a single one. Seems to me that people in general think that wrapping the bitmask with a struct is too much.
Are you speaking of https://lists.boost.org/archives/list/boost@lists.boost.org/thread/EZXQFJWA5... ? It did receive some positive feedback. But the approach was not the one proposed by Janko. And i'm starting to consider the flag_set approach to be a far superior one. bit flags are not a value per se, they are a set of values, and they deserve to be treated as such. Regards, Julien
В письме от вторник, 18 ноября 2025 г. 13:46:22 MSK пользователь Janko Dedic via Boost написал:
enum class E { A, B, C }; flag_set<E> flags{E::A, E::B}; flags.add(E::C); flags.erase(E::A); bool b = flags.contains(E::B);
There was recently (last year?) a library author seeking for endorsements for a library like that. To my knowledge, he didn't get a single one. Seems to me that people in general think that wrapping the bitmask with a struct is too much. (Re-sending as my email was mangled by my email client)
participants (6)
-
Andrey Semashev -
Dmitry Arkhipov -
Janko Dedic -
Julien Blanc -
Peter Dimov -
Ruben Perez