[Describe] Review - Part 1: Enumerators
Hi Everyone, This is part one of my review of Boost.Describe. I will focus only on dealing with enumerators. Working with C++ enumerations is harder than one might think, because they are not what many people believe. They are just a bunch of integral constants and an integral type. We can get things like this: enum Season { Spring = 1, Summer, Autumn, // British Fall = Autumn, // American Winter, }; struct Human { std::string name; Season born; }; int main() { Human h {}; inspect(h); } void inspect(Human h) { switch(h) { case Spring: case Summer: case Fall: case Winter: return; default: assert(false) } } I can trigger an assertion failure without even doing any cast, because enums can assume other values that those listed in the declaration. The low-level interface provided by Boost.Describe is able to manage all these aspects very nicely. If we call BOOST_DESCRIBE_ENUM on struct Season above, we will get a list of five elements and the user will have to decide what she wants to do with it. On the other hand, requesting that Boost.Describe should provide a higher level enum-to-string mapping seems to overlook the special rules of C++ enums. How do you want this function to behave if the input enum value is not found? What do you want the function to do if it finds more than one corresponding string? The interface of describe_enumerators<> is optimal. Macro BOOST_DESCRIBE_ENUM is redundant: BOOST_DESCRIBE_ENUM_CLASS can handle unscoped enumerations pretty well. Note that in C++11 you can use the qualified names of unscoped enumerators, like Season::Winter in the above example. After playing first with macros for describing structs, I was surprised that the syntax for describing enumerators doesn't use parentheses for the list of enumerators: BOOST_DESCRIBE_ENUM(X, (A, B, C)); // I somehow expected that to work. Describing enums from the global namespace introduces names starting with underscore (_enum_descriptor_fn) into global namespace. This is Undefined Behavior, and is likely to trigger some code sanitizers. Instead use an ugly name, like boost_describe_enum_descriptor_fn. I do not appreciate the BOOST_DEFINE_ macros for enumerations. We get as much as four of them, and neither is able to handle the use case that I consider basic, which I often have in my code: where I omit value zero and start from 1: struct Error { badSoething = 1, badSomethingElse, tooLongSomethng, ... }; So, I am not sure how many real life enums these macros cover. Regards, &rzej;
Working with C++ enumerations is harder than one might think, because they are not what many people believe. They are just a bunch of integral constants and an integral type. We can get things like this:
enum Season { Spring = 1, Summer, Autumn, // British Fall = Autumn, // American Winter, }; Valid concern, however I think the most part are actually "real enumerators": A one out of a set of values. (I wish C++11 had used enum classes for that instead, missed opportunity IMO) rough sketch: It doesn't really matter if your enum starts at 1, contrary: It might be faster if it does not (for default init to first value and switch-cases). Fall==Autumn is rather a UI than a code issue. But don't want to go to deep, as you are right in principle: Enums in C++ are not enumerators. The interface of describe_enumerators<> is optimal. +1 BOOST_DESCRIBE_ENUM(X, (A, B, C)); // I somehow expected that to work. IMO that makes the interface bulkier than needed and likely slower to compile/preprocess. For structs/classes you need that due to multiple argument lists Describing enums from the global namespace introduces names starting with underscore (_enum_descriptor_fn) into global namespace. This is Undefined Behavior, and is likely to trigger some code sanitizers. Instead use an ugly name, like boost_describe_enum_descriptor_fn. Double underscore or underscore+capital is UB, this here isn't. However I agree with the name hogging as there MIGHT be user defined functions of that name. I do not appreciate the BOOST_DEFINE_ macros for enumerations. We get as much as four of them, and neither is able to handle the use case that I consider basic, which I often have in my code: where I omit value zero and start from 1:
struct Error { badSoething = 1, badSomethingElse, tooLongSomethng, ... };
So, I am not sure how many real life enums these macros cover.
IMO they are valueable. As much as I dislike macros it avoids the potential errors by forgetting to update the describe part. Again: I don't think starting at 1 (or anything specific) is useful if you really need an enumeration of values. It might be a convention for some reasons, but I'd guess the most common case is actually the start at "anything" (i.e. 0) case, at least that is my experience. Exceptions I've found are legacy code, bit fields, mapping to system values (error codes) and similar. And as those macros are very lightweight I appreciate their existence. Just another view from a user :)
śr., 10 mar 2021 o 17:50 Alexander Grund via Boost
Working with C++ enumerations is harder than one might think, because they are not what many people believe. They are just a bunch of integral constants and an integral type. We can get things like this:
enum Season { Spring = 1, Summer, Autumn, // British Fall = Autumn, // American Winter, }; Valid concern, however I think the most part are actually "real enumerators": A one out of a set of values. (I wish C++11 had used enum classes for that instead, missed opportunity IMO) rough sketch: It doesn't really matter if your enum starts at 1, contrary: It might be faster if it does not (for default init to first value and switch-cases). Fall==Autumn is rather a UI than a code issue. But don't want to go to deep, as you are right in principle: Enums in C++ are not enumerators. The interface of describe_enumerators<> is optimal. +1 BOOST_DESCRIBE_ENUM(X, (A, B, C)); // I somehow expected that to work. IMO that makes the interface bulkier than needed and likely slower to compile/preprocess. For structs/classes you need that due to multiple argument lists Describing enums from the global namespace introduces names starting with underscore (_enum_descriptor_fn) into global namespace. This is Undefined Behavior, and is likely to trigger some code sanitizers. Instead use an ugly name, like boost_describe_enum_descriptor_fn. Double underscore or underscore+capital is UB, this here isn't. However I agree with the name hogging as there MIGHT be user defined functions of that name.
http://eel.is/c++draft/lex.name#3.2
I do not appreciate the BOOST_DEFINE_ macros for enumerations. We get as
much as four of them, and neither is able to handle the use case that I consider basic, which I often have in my code: where I omit value zero and start from 1:
struct Error { badSoething = 1, badSomethingElse, tooLongSomethng, ... };
So, I am not sure how many real life enums these macros cover.
IMO they are valueable. As much as I dislike macros it avoids the potential errors by forgetting to update the describe part.
Again: I don't think starting at 1 (or anything specific) is useful if you really need an enumeration of values. It might be a convention for some reasons, but I'd guess the most common case is actually the start at "anything" (i.e. 0) case, at least that is my experience. Exceptions I've found are legacy code, bit fields, mapping to system values (error codes) and similar.
The idea behind omitting zero is that we then can tell apart any proper enum value from the situation where we have default-initialized an enum and forgot to put a proper value. Regards, &rzej;
And as those macros are very lightweight I appreciate their existence.
Just another view from a user :)
_______________________________________________ Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost
Andrzej Krzemienski wrote:
Macro BOOST_DESCRIBE_ENUM is redundant: BOOST_DESCRIBE_ENUM_CLASS can handle unscoped enumerations pretty well. Note that in C++11 you can use the qualified names of unscoped enumerators, like Season::Winter in the above example.
Thanks to everyone who pointed this out, I'll gladly remove the redundant macro. This is why we have Boost reviews. :-)
Andrzej Krzemienski wrote:
I do not appreciate the BOOST_DEFINE_ macros for enumerations. We get as much as four of them, ...
We'll even get eight once the requested support for class-nested enums is added. :-)
and neither is able to handle the use case that I consider basic, which I often have in my code: where I omit value zero and start from 1:
struct Error { badSoething = 1, badSomethingElse, tooLongSomethng, ... };
So, I am not sure how many real life enums these macros cover.
They cover the common case of an enumeration without any initializers; the
case where you have a number of alternatives but don't care about their exact
numeric values. I do believe this is reasonably common, even if it's not
universal.
E.g. enum class Shape { Triangle, Rectangle, Circle }.
Further e.g.
template
participants (3)
-
Alexander Grund
-
Andrzej Krzemienski
-
Peter Dimov