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.