Interest in a library for type-safe bitwise operations?
Hi, I would like to gauge interest in a library that makes the usage of flag-like enumerations safer. The library is called Boost.Flags, and its key features are - provides type-safe bitwise operations for flag-like scoped / unscoped enumerations - turns undetected logical errors into type errors Further characteristics are - non-intrusive - only a function overload or a template specialization required to opt-in - single header-file library - no dependencies (only standard-library includes) - everything is constexpr - zero-overhead in optimized builds - requires at least C++11, uses newer features if available From the Boost.Flags documentation:
Why do we use the bits of integer types for Boolean options?
- easy to define, e.g. as macros, integer constants or enumerators - language support for setting and querying through bitwise operators - compact representation in memory - simple bulk transfer in interfaces (on many platforms an int can hold up to 32 different Boolean options) - enforces the usage of names, especially to prevent bool parameters in interfaces: - "Boolean arguments loudly declare that the function does more than one thing. They are confusing and should be eliminated." [C. Martin, Clean Code]
But C++ already has everything to work with integer-based flags, so why do we need Boost.Flags? The answer is type-safety! With integer-based flags we can do too much. E.g. when there are multiple different integer-based flag sets, nothing prevents the programmer from accidentally mixing flags from unrelated flag-sets. And of course, there are problems with the bitwise negation operator~. If you want to know, what possibly could go wrong, read the docs. The Github page: https://github.com/tobias-loew/flags The online docs: https://tobias-loew.github.io/flags Example on Godbolt: https://godbolt.org/z/qbojncnd7 I am looking forward to your comments! Tobias
On 23.03.24 08:02, Tobias Loew via Boost wrote:
I would like to gauge interest in a library that makes the usage of flag-like enumerations safer.
I am currently using my own macro-based code for this, and I'm not entirely happy with it. If you can do better, then I am very much interested in seeing your library in Boost! -- Rainer Deyke (rainerd@eldwood.com)
сб, 23 мар. 2024 г. в 15:52, Tobias Loew via Boost
Hi,
I would like to gauge interest in a library that makes the usage of flag-like enumerations safer. The library is called Boost.Flags, and its key features are ... Example on Godbolt: https://godbolt.org/z/qbojncnd7
From the reading of the docs I gathered that the library relies on operators in the global namespace, rather than using a macro to define operators in an enum's namespace. This has a certain flaw: https://godbolt.org/z/xePjK3es6
Am 2024-03-23 15:07, schrieb Дмитрий Архипов via Boost:
сб, 23 мар. 2024 г. в 15:52, Tobias Loew via Boost
: Hi,
I would like to gauge interest in a library that makes the usage of flag-like enumerations safer. The library is called Boost.Flags, and its key features are ... Example on Godbolt: https://godbolt.org/z/qbojncnd7
From the reading of the docs I gathered that the library relies on operators in the global namespace, rather than using a macro to define operators in an enum's namespace. This has a certain flaw: https://godbolt.org/z/xePjK3es6
The described flaw can be avoided by either including flags.hpp before `foobar` or by `using ::operator |` in the enums namespace. (https://godbolt.org/z/rT5678n4f) I could add a macro the enables ADL for the current namespace, e.g. #define BOOST_FLAGS_ENABLE_ADL_FOR_NS \ using ::operator| \ using ::operator& \ using ::operator^ \ ... and so on
On 3/24/24 02:22, Tobias Loew via Boost wrote:
Am 2024-03-23 15:07, schrieb Дмитрий Архипов via Boost:
сб, 23 мар. 2024 г. в 15:52, Tobias Loew via Boost
: Hi,
I would like to gauge interest in a library that makes the usage of flag-like enumerations safer. The library is called Boost.Flags, and its key features are ... Example on Godbolt: https://godbolt.org/z/qbojncnd7
From the reading of the docs I gathered that the library relies on operators in the global namespace, rather than using a macro to define operators in an enum's namespace. This has a certain flaw: https://godbolt.org/z/xePjK3es6
The described flaw can be avoided by either including flags.hpp before `foobar` or by `using ::operator |` in the enums namespace. (https://godbolt.org/z/rT5678n4f)
I could add a macro the enables ADL for the current namespace, e.g.
#define BOOST_FLAGS_ENABLE_ADL_FOR_NS \ using ::operator| \ using ::operator& \ using ::operator^ \ ... and so on
I don't really like the idea of operators in the global namespace. I would rather prefer if the operators were defined in the library's namespace, and the enablement was always through the macro. Where the macro would not just import the operators for lookup but also "enable" them for the given enum.
Am 2024-03-25 11:38, schrieb Andrey Semashev via Boost:
On 3/24/24 02:22, Tobias Loew via Boost wrote:
Am 2024-03-23 15:07, schrieb Дмитрий Архипов via Boost:
сб, 23 мар. 2024 г. в 15:52, Tobias Loew via Boost
: Hi,
I would like to gauge interest in a library that makes the usage of flag-like enumerations safer. The library is called Boost.Flags, and its key features are ... Example on Godbolt: https://godbolt.org/z/qbojncnd7
From the reading of the docs I gathered that the library relies on operators in the global namespace, rather than using a macro to define operators in an enum's namespace. This has a certain flaw: https://godbolt.org/z/xePjK3es6
The described flaw can be avoided by either including flags.hpp before `foobar` or by `using ::operator |` in the enums namespace. (https://godbolt.org/z/rT5678n4f)
I could add a macro the enables ADL for the current namespace, e.g.
#define BOOST_FLAGS_ENABLE_ADL_FOR_NS \ using ::operator| \ using ::operator& \ using ::operator^ \ ... and so on
I don't really like the idea of operators in the global namespace. I would rather prefer if the operators were defined in the library's namespace, and the enablement was always through the macro. Where the macro would not just import the operators for lookup but also "enable" them for the given enum.
The library now has a macro-option to prevent importing all operators into the global namespace (BOOST_FLAGS_NO_GLOBAL_USING). Additionally, there are macros to easily import all operators/utility functions into a namespace (BOOST_FLAGS_USING_OPERATORS(), BOOST_FLAGS_USING_UTILITIES()). I'm not sure what you mean by your last sentence. Do you just ask for a macro, like #define BOOST_FLAGS_ENABLE(Enum) \ constexpr inline bool boost_flags_enable(Enum) { return true; } \ BOOST_FLAGS_USING_OPERATORS() or something else?
On 3/26/24 18:53, Tobias Loew via Boost wrote:
Am 2024-03-25 11:38, schrieb Andrey Semashev via Boost:
I don't really like the idea of operators in the global namespace. I would rather prefer if the operators were defined in the library's namespace, and the enablement was always through the macro. Where the macro would not just import the operators for lookup but also "enable" them for the given enum.
The library now has a macro-option to prevent importing all operators into the global namespace (BOOST_FLAGS_NO_GLOBAL_USING).
I think the no-global-namespace should be the only option, and there should be no such macro. Otherwise, there is risk of configuration mismatch and ODR violations. Enums are often used in library interfaces, and offering this configuration macro may pose a problem. For example, if a library A wants to define its public enums with this macro defined, and its user B wants to define its own enums without this macro then there will be a compatibility issue.
Additionally, there are macros to easily import all operators/utility functions into a namespace (BOOST_FLAGS_USING_OPERATORS(), BOOST_FLAGS_USING_UTILITIES()).
I'm not sure what you mean by your last sentence. Do you just ask for a macro, like
#define BOOST_FLAGS_ENABLE(Enum) \ constexpr inline bool boost_flags_enable(Enum) { return true; } \ BOOST_FLAGS_USING_OPERATORS()
or something else?
Yes, or something equivalent. I'm not sure this can be made working in all contexts, e.g. for enums declared in user's namespaces as well as in classes and templates, so there may be multiple such macros for different use cases or there may be optional arguments or something. But the effect should be the same - you apply one macro to the enum, and that makes bitwise operators work for that enum. I'm not sure what would be the purpose of the other macros you mention (BOOST_FLAGS_USING_OPERATORS, BOOST_FLAGS_USING_UTILITIES). I mean, is there a use case for importing the operators but not enabling them? And what are the utilities and why one would want a macro to import them?
Am 2024-03-26 18:50, schrieb Andrey Semashev via Boost:
On 3/26/24 18:53, Tobias Loew via Boost wrote:
Am 2024-03-25 11:38, schrieb Andrey Semashev via Boost:
I don't really like the idea of operators in the global namespace. I would rather prefer if the operators were defined in the library's namespace, and the enablement was always through the macro. Where the macro would not just import the operators for lookup but also "enable" them for the given enum.
The library now has a macro-option to prevent importing all operators into the global namespace (BOOST_FLAGS_NO_GLOBAL_USING).
I think the no-global-namespace should be the only option, and there should be no such macro. Otherwise, there is risk of configuration mismatch and ODR violations.
Enums are often used in library interfaces, and offering this configuration macro may pose a problem. For example, if a library A wants to define its public enums with this macro defined, and its user B wants to define its own enums without this macro then there will be a compatibility issue.
Thanks for pointing out this, even though I cannot see ODR violations: the definition of the operators/utility functions are always the same and reside in namespace boost::flags, the import into other namespaces is done via using declarations which (IIRC) are irrelevant to ODR. One more word to, why I imported everything into global namespaces: I wanted the opt-in be a single definition and NOT a macro (just to prove, that it's possible). So, to make it work, I had to import all operators/functions into global namespace. From my perspective, this library is more like a core-language extension: it introduces flags to the language in a generic way, and therefore enhances the global operators with the respective functionality for enabled enums.
Additionally, there are macros to easily import all operators/utility functions into a namespace (BOOST_FLAGS_USING_OPERATORS(), BOOST_FLAGS_USING_UTILITIES()).
I'm not sure what you mean by your last sentence. Do you just ask for a macro, like
#define BOOST_FLAGS_ENABLE(Enum) \ constexpr inline bool boost_flags_enable(Enum) { return true; } \ BOOST_FLAGS_USING_OPERATORS()
or something else?
Yes, or something equivalent. I'm not sure this can be made working in all contexts, e.g. for enums declared in user's namespaces as well as in classes and templates, so there may be multiple such macros for different use cases or there may be optional arguments or something. But the effect should be the same - you apply one macro to the enum, and that makes bitwise operators work for that enum.
I'm not sure what would be the purpose of the other macros you mention (BOOST_FLAGS_USING_OPERATORS, BOOST_FLAGS_USING_UTILITIES). I mean, is there a use case for importing the operators but not enabling them? And what are the utilities and why one would want a macro to import them?
I though it the other way around: enabling without importing. When everything is imported into global namespace by default, then it's only under certain conditions (e.g. the one you pointed out) necessary to also import into the enum's namespace. The utilities are a bunch of functions - for handling flags as a multi-dim Boolean algebra: tests for inclusion, intersection - easy interface for modifying flags depending on a bool, e.g. make_if(E e, bool set) for set ? e : E{} remove_if(E e, E mod, bool remove) for remove ? e & ~mod : e I see them as part of the flags interface, so in my original version of the library they were always imported into global namespace. But I wanted the import to be optional, in case others see it different.
On 3/27/24 17:01, Tobias Loew via Boost wrote:
Am 2024-03-26 18:50, schrieb Andrey Semashev via Boost:
On 3/26/24 18:53, Tobias Loew via Boost wrote:
The library now has a macro-option to prevent importing all operators into the global namespace (BOOST_FLAGS_NO_GLOBAL_USING).
I think the no-global-namespace should be the only option, and there should be no such macro. Otherwise, there is risk of configuration mismatch and ODR violations.
Enums are often used in library interfaces, and offering this configuration macro may pose a problem. For example, if a library A wants to define its public enums with this macro defined, and its user B wants to define its own enums without this macro then there will be a compatibility issue.
Thanks for pointing out this, even though I cannot see ODR violations: the definition of the operators/utility functions are always the same and reside in namespace boost::flags, the import into other namespaces is done via using declarations which (IIRC) are irrelevant to ODR.
It may be relevant to ODR if the using-declaration changes the operator found by name lookup.
One more word to, why I imported everything into global namespaces: I wanted the opt-in be a single definition and NOT a macro (just to prove, that it's possible). So, to make it work, I had to import all operators/functions into global namespace.
I understand. But being able to do it doesn't necessarily make it the best solution. Introducing things in global namespace is rather intrusive, and it has bitten us in the back in the past. I think, libraries should avoid putting their stuff in the global namespace at all costs, and the "enable" macro approach seems like an acceptable alternative to me.
From my perspective, this library is more like a core-language extension: it introduces flags to the language in a generic way, and therefore enhances the global operators with the respective functionality for enabled enums.
Even core language extension libraries should avoid polluting the global namespace. Take existing Boost libraries for example: Boost.ForEach, Boost.Lambda2, Boost.StaticAssert. By the way, we already have boost/detail/bitmask.hpp that provides bitwise operators for enums, and it is also designed to be used in the enum's namespace (i.e. without polluting the global namespace).
The utilities are a bunch of functions - for handling flags as a multi-dim Boolean algebra: tests for inclusion, intersection - easy interface for modifying flags depending on a bool, e.g. make_if(E e, bool set) for set ? e : E{} remove_if(E e, E mod, bool remove) for remove ? e & ~mod : e
I see them as part of the flags interface, so in my original version of the library they were always imported into global namespace. But I wanted the import to be optional, in case others see it different.
This sounds more like a collection of algorithms on enums. In this case, I think, they are better placed in Boost.Flags namespace, and there is no need to import them, other than to omit the namespace when calling them.
Am 2024-03-27 17:40, schrieb Andrey Semashev via Boost:
On 3/27/24 17:01, Tobias Loew via Boost wrote:
Am 2024-03-26 18:50, schrieb Andrey Semashev via Boost:
On 3/26/24 18:53, Tobias Loew via Boost wrote:
The library now has a macro-option to prevent importing all operators into the global namespace (BOOST_FLAGS_NO_GLOBAL_USING).
I think the no-global-namespace should be the only option, and there should be no such macro. Otherwise, there is risk of configuration mismatch and ODR violations.
Enums are often used in library interfaces, and offering this configuration macro may pose a problem. For example, if a library A wants to define its public enums with this macro defined, and its user B wants to define its own enums without this macro then there will be a compatibility issue.
Thanks for pointing out this, even though I cannot see ODR violations: the definition of the operators/utility functions are always the same and reside in namespace boost::flags, the import into other namespaces is done via using declarations which (IIRC) are irrelevant to ODR.
It may be relevant to ODR if the using-declaration changes the operator found by name lookup.
I changed the enabling-function at namespace scope to a macro that also imports all the operators into the current namespace. So, all operator call can be found by ADL. As this is not possible at class scope, I swallowed the toad (no harm to amphibia was done) and made a macro that enables and adds forwarding friend functions. This is the only way I could find to ensure ADL for enums in classes with just a single macro right after the enum definition inside the class.
One more word to, why I imported everything into global namespaces: I wanted the opt-in be a single definition and NOT a macro (just to prove, that it's possible). So, to make it work, I had to import all operators/functions into global namespace.
I understand. But being able to do it doesn't necessarily make it the best solution. Introducing things in global namespace is rather intrusive, and it has bitten us in the back in the past. I think, libraries should avoid putting their stuff in the global namespace at all costs, and the "enable" macro approach seems like an acceptable alternative to me.
Agree, see above.
From my perspective, this library is more like a core-language extension: it introduces flags to the language in a generic way, and therefore enhances the global operators with the respective functionality for enabled enums.
Even core language extension libraries should avoid polluting the global namespace. Take existing Boost libraries for example: Boost.ForEach, Boost.Lambda2, Boost.StaticAssert.
By the way, we already have boost/detail/bitmask.hpp that provides bitwise operators for enums, and it is also designed to be used in the enum's namespace (i.e. without polluting the global namespace).
That's interesting. I use the Boost libraries (many of them) now for almost 20 years and never came over that file. I've added it to the "Other flags-like enum implementations" section in my docs. Nevertheless, my library is superior to bitmask.hpp in several ways: - complement can be distinguished - enhanced type-safety for unscoped enums: with my library an enabled enum cannot be combined with a non-compatible enum for binary operators - bitmask.hpp may invoke UB for operator~ (Boost.Flags does not, when complement is not disabled) - support for "large" enums (size > 32 bits), bitmask.hpp silently returns wrong values - everything is constexpr
The utilities are a bunch of functions - for handling flags as a multi-dim Boolean algebra: tests for inclusion, intersection - easy interface for modifying flags depending on a bool, e.g. make_if(E e, bool set) for set ? e : E{} remove_if(E e, E mod, bool remove) for remove ? e & ~mod : e
I see them as part of the flags interface, so in my original version of the library they were always imported into global namespace. But I wanted the import to be optional, in case others see it different.
This sounds more like a collection of algorithms on enums. In this case, I think, they are better placed in Boost.Flags namespace, and there is no need to import them, other than to omit the namespace when calling them.
Am 2024-03-26 18:50, schrieb Andrey Semashev via Boost:
On 3/26/24 18:53, Tobias Loew via Boost wrote:
Am 2024-03-25 11:38, schrieb Andrey Semashev via Boost:
I don't really like the idea of operators in the global namespace. I would rather prefer if the operators were defined in the library's namespace, and the enablement was always through the macro. Where the macro would not just import the operators for lookup but also "enable" them for the given enum.
The library now has a macro-option to prevent importing all operators into the global namespace (BOOST_FLAGS_NO_GLOBAL_USING).
I think the no-global-namespace should be the only option, and there should be no such macro. Otherwise, there is risk of configuration mismatch and ODR violations.
Enums are often used in library interfaces, and offering this configuration macro may pose a problem. For example, if a library A wants to define its public enums with this macro defined, and its user B wants to define its own enums without this macro then there will be a compatibility issue.
Additionally, there are macros to easily import all operators/utility functions into a namespace (BOOST_FLAGS_USING_OPERATORS(), BOOST_FLAGS_USING_UTILITIES()).
I'm not sure what you mean by your last sentence. Do you just ask for a macro, like
#define BOOST_FLAGS_ENABLE(Enum) \ constexpr inline bool boost_flags_enable(Enum) { return true; } \ BOOST_FLAGS_USING_OPERATORS()
or something else?
Yes, or something equivalent. I'm not sure this can be made working in all contexts, e.g. for enums declared in user's namespaces as well as in classes and templates, so there may be multiple such macros for different use cases or there may be optional arguments or something. But the effect should be the same - you apply one macro to the enum, and that makes bitwise operators work for that enum.
When the operators are not imported into global namespace, enabling ADL for enums in classes is a bit of a problem, since there doesn't exist a class-local `using` declaration. So, one would either need a macro which generates friend-proxies for each operator friend constexpr local_enum operator&(local_enum l, local_enum r) { return ::boost::flags::operator&(l, r); } and so on, which add an additional indirection and defines a lot of new operators. Or a using declaration before or after the class, which is non-local. Both options don't seem really nice. At least, I found a way, to check inside the class that ADL is working. (Here to keep it short only for operator&) namespace n { using ::boost::flags::operator&; struct m { enum class local_enum { p = 1, }; friend constexpr inline bool boost_flags_enable(local_enum) { return true; } friend constexpr void boost_flags_adl_check() { static_assert([](local_enum a) { return (a = a & a, true); }(local_enum{}), ""); } }; } That way the enabling and check could be in a single macro right after the definition of the enum.
Le samedi 23 mars 2024 à 08:02 +0100, Tobias Loew via Boost a écrit :
Hi,
I would like to gauge interest in a library that makes the usage of flag-like enumerations safer. The library is called Boost.Flags, and its key features are
The Github page: https://github.com/tobias-loew/flags The online docs: https://tobias-loew.github.io/flags Example on Godbolt: https://godbolt.org/z/qbojncnd7
I am looking forward to your comments!
That's definitely useful. Repeating the writing of these operators is just a waste of time. And i really like the idea that operator~ returns a different type. The doc is also pretty good. I would really like to see this library into boost. Regards, Julien
participants (5)
-
Andrey Semashev
-
Julien Blanc
-
Rainer Deyke
-
tobi@die-loews.de
-
Дмитрий Архипов