[enums] Request for comments on an enum-oriented library

Hi all, I'm currently writing a C++ library to add some functionalities to enums, and am looking for comments and feedbacks. Boost is a reference, and I would be glad if some C++ expert here could take a moment to look at what I've written and tell me his thoughts. I know that there was already some discussions about enums here, and some early implementations of enum libraries at Boost, but I couldn't find any that fitted my requirements, or any that was in an stable enough stage. My goal wasn't to submit another one to Boost (although I would be really proud of it, I'm not thinking about it yet, just looking for comments), but to implement something usable for some of my specific needs. Initially I was only looking for enum (de)serialization, however I think that these needs can be quite common, and that the library could be used by others as well. That's why I'm trying to find some comments and to share my work. My goal was to implement a library following these requirements : - Extend the enum concept with different flavors. - Scope the enum constants out of namespace scope. - Do not get rid of implicit integer cast, or at least, try to provide enum/integer interoperability so we don't have to cast all the time. - No overhead, keep the enum constants as light as they are natively. For the first requirement, I wanted to add three new flavors to enums, that I've called "static enums", "dynamic enums" and "bit fields". The main difference between them is the way these enums are (de)serialized and the available enum / enum and enum / integer operators when this is meaningful (only when using C++0x, with scoped enums). - Static enums only allow explicitly defined constants (with specified or implicit integer values), any other value will result in a (de)serialization to special value "unknown". - Dynamic enums allow explicitly defined constants and offsets from these constants, within a given global range (from the first constant to the specified upper integer limit). Every value within the range will be serializable, any other will result in "unknown". An integer value between two enum constants will be (de)serialized as the nearest lower enum constant plus an offset. For example, if the enum is defined as { value1 = 1, value2 = 5..., unknown = 10 }, the available range would be from 1 to 9, and the integer 4 will be serialized as "value1#3" which means (value1 + 3). - Bit fields enums only allow combination of explicitely defined constants. These will be (de)serialized as a combination of every defined enum constants, any other value will be (de)serialized as "unknown". This flavor restricts constants values to bitwise distinct integers, and to values different from 0 in order to be able to decompose the enum into the specified enum constants. 0 being the special "unknown" value for this flavor. For example, for an enum defined as { bit1 = 0b001, bit2 = 0b010, bit3 = 0b100... }, an instance of this enum with an integer value of 3 will be serialized as "bit1+bit2", and "bit1+bit3" will be deserialized as an enum instance of integer value 5. As I said, the library obviously uses Boost, and in an extensive way Boost Preprocessor macros, and tries to integrate nicely with Boost libraries. Serialization, for example, is done through stream template operators, and should be usable with the Boost Lexical Cast library. These isn't any real documentation yet, except from doxygen (although not yet complete, I've tried to be verbose), but I think that the library is simple enough to be understood by reading the source code, and I've got some unit tests to help getting the genereal idea. Lastly, I still have some issues about some points, mostly because of the use of macros and boilerplate code, but I hope to be able to fix them. - No namespace support. - Enum declaration comes with the serialization code. I would like to be able to separate definition and declaration of this code, but I'm not sure how to do that properly without overhead for the developer. I'll have to think about it. - The writing overhead of the enum declaration (compared to plain old enum) is still a little bit too much for my taste, but I don't know if I'll be able to do much better without introducing some obscure and meaningless helper macros. This is mainly because of the need of declaring the enum constants in a Boost Preprocessor sequence, in order to be able to use it at various places to generate the enum declaration as well as the serialization code. - Also, the declaration could be cleaner if I could find a way to implement optional parameters in macros, but that seems like difficult to do (or I haven't found out yet how to do). - I don't have any other environment available except from my Debian Linux box, so I couldn't test on anything else than gcc. I don't really expect it to work with anything else. Actually, my main build scripts themselves are probably broken on anything else than my computer. [1] That said, if anyone wants to take a look at it and share his thoughts, the library can be found here : https://github.com/berenm/libtenum Best regards, Beren Minor Notes: [1] I've added a "portable-build" branch to my git repo if anyone want to try running the tests cases somewhere else. Build is done with bjam.

Beren Minor wrote:
Hi all,
I'm currently writing a C++ library to add some functionalities to enums, and am looking for comments and feedbacks. Boost is a reference, and I would be glad if some C++ expert here could take a moment to look at what I've written and tell me his thoughts.
I know that there was already some discussions about enums here, and some early implementations of enum libraries at Boost, but I couldn't find any that fitted my requirements, or any that was in an stable enough stage.
I don't know if you know my development of Boost.Enums on the sandbox (https://svn.boost.org/svn/boost/sandbox/enums/libs/enums/doc/html/index.html). Were you aware of it? The library is not stable enough and doesn't takes care of stream I/O yet. Output to a stream is quite simple to add as the library provides enum to string conversion. Input from a stream will be a little more complex in the general case, but quite simple if the associated string doesn't contains white characters. I'm not sure the stream I/O of enums shouldn't use directly the string form. A manipulator could be used to this respect. I need to think about it a little more.
My goal wasn't to submit another one to Boost (although I would be really proud of it, I'm not thinking about it yet, just looking for comments), but to implement something usable for some of my specific needs. Initially I was only looking for enum (de)serialization, however I think that these needs can be quite common, and that the library could be used by others as well. That's why I'm trying to find some comments and to share my work.
I'm not sure the word serialization is the most appropriated in your case, as what you provide is stream I/O. Anyway the library should provide also serialization as defined in Boost.Serialization. I will add this to my to do list.
My goal was to implement a library following these requirements :
- Extend the enum concept with different flavors. - Scope the enum constants out of namespace scope. - Do not get rid of implicit integer cast, or at least, try to provide enum/integer interoperability so we don't have to cast all the time. - No overhead, keep the enum constants as light as they are natively.
For the first requirement, I wanted to add three new flavors to enums, that I've called "static enums", "dynamic enums" and "bit fields". The main difference between them is the way these enums are (de)serialized and the available enum / enum and enum / integer operators when this is meaningful (only when using C++0x, with scoped enums).
- Static enums only allow explicitly defined constants (with specified or implicit integer values), any other value will result in a (de)serialization to special value "unknown".
IIUC this is an enum that can take only the enum literals, isn't it? If yes, I suggest an alternative name: strong_enum. Note: My library doesn't provide strong enums.
- Dynamic enums allow explicitly defined constants and offsets from these constants, within a given global range (from the first constant to the specified upper integer limit). Every value within the range will be serializable, any other will result in "unknown". An integer value between two enum constants will be (de)serialized as the nearest lower enum constant plus an offset. For example, if the enum is defined as { value1 = 1, value2 = 5..., unknown = 10 }, the available range would be from 1 to 9, and the integer 4 will be serialized as "value1#3" which means (value1 + 3).
IIUC this is a classical enum that allow to store other values than the enum literals. I suggest to call them just enum. The specificity is associated to I/O on streams. I guess that this feature could be orthogonal to the class.
- Bit fields enums only allow combination of explicitely defined constants. These will be (de)serialized as a combination of every defined enum constants, any other value will be (de)serialized as "unknown". This flavor restricts constants values to bitwise distinct integers, and to values different from 0 in order to be able to decompose the enum into the specified enum constants. 0 being the special "unknown" value for this flavor. For example, for an enum defined as { bit1 = 0b001, bit2 = 0b010, bit3 = 0b100... }, an instance of this enum with an integer value of 3 will be serialized as "bit1+bit2", and "bit1+bit3" will be deserialized as an enum instance of integer value 5.
In my library this type should correspond to an enum_set, which offer a safe interface for ordinal enums.
As I said, the library obviously uses Boost, and in an extensive way Boost Preprocessor macros, and tries to integrate nicely with Boost libraries. Serialization, for example, is done through stream template operators, and should be usable with the Boost Lexical Cast library. These isn't any real documentation yet, except from doxygen (although not yet complete, I've tried to be verbose), but I think that the library is simple enough to be understood by reading the source code, and I've got some unit tests to help getting the general idea.
Lastly, I still have some issues about some points, mostly because of the use of macros and boilerplate code, but I hope to be able to fix them.
- No namespace support.
I guess this is what the standard call scoped enums, isn't it? My library provides an emulation of scoped enums with some limitations. Some syntactic workarounds need to be used to overcome the limitations.
- Enum declaration comes with the serialization code. I would like to be able to separate definition and declaration of this code, but I'm not sure how to do that properly without overhead for the developer. I'll have to think about it.
I see two options. Either the library provide two set of macros, with and without serialization, or the library is able to generate the serialization code from some static information provided by the basic macros. I will see how this can be done with Boost.Enums.
- The writing overhead of the enum declaration (compared to plain old enum) is still a little bit too much for my taste, but I don't know if I'll be able to do much better without introducing some obscure and meaningless helper macros. This is mainly because of the need of declaring the enum constants in a Boost Preprocessor sequence, in order to be able to use it at various places to generate the enum declaration as well as the serialization code. - Also, the declaration could be cleaner if I could find a way to implement optional parameters in macros, but that seems like difficult to do (or I haven't found out yet how to do).
The syntax associated to this example lte_enum(static_enum, lte_ev(value1,2) lte_e(value2) lte_en(value3,"value_number_3"), ::boost::uint8_t ) is with my library something like BOOST_ENUM_TYPE(static_enum,::boost::uint8_t, ( (value1) (2) ) ( (value2) ) ( (value3) () ("value_number_3") ) I don't know if this is more readable to you. On compiler providing variadic macros I expect to be able to provide also this alternative BOOST_ENUM_TYPE(static_enum,::boost::uint8_t, (value1) (2), value2, (value3) () ("value_number_3") ) Note the use of () to denote the default value for the 3rd literal.
Notes: [1] I've added a "portable-build" branch to my git repo if anyone want to try running the tests cases somewhere else. Build is done with bjam.
I was looking for the Jamfile to run the test. I see now that there is a Jamroot file on the root. It would be better to provide a Jamfile for the test directory as the other Boost libraries do. I will try to run it. Best, Vicente -- View this message in context: http://boost.2283326.n4.nabble.com/enums-Request-for-comments-on-an-enum-ori... Sent from the Boost - Dev mailing list archive at Nabble.com.

Thanks for your comments Vincente, I'll try to answer to some points.
I don't know if you know my development of Boost.Enums on the sandbox (https://svn.boost.org/svn/boost/sandbox/enums/libs/enums/doc/html/index.html). Were you aware of it?
Yes, as I said, I know that there already are some enum libraries in development at Boost. I didn't know exactly how stable and how active was the development though. My point wasn't to offer another one but to get some comments on my code and on the design choices I took. I've had a quick look at yours, but there were some point refraining me from using it, like stream I/O, no integer implicit conversion.
The library is not stable enough and doesn't takes care of stream I/O yet. Output to a stream is quite simple to add as the library provides enum to string conversion. Input from a stream will be a little more complex in the general case, but quite simple if the associated string doesn't contains white characters. I'm not sure the stream I/O of enums shouldn't use directly the string form. A manipulator could be used to this respect. I need to think about it a little more.
Stream I/O was a very important requirement to me. Initially, I've coded the library to help me reading and writing key mappings from and to a configuration file. The keys were represented by enums, and could be modified with a bit field combination of modifier keys (alt, ctrl, shift...). I did want to have a straightforward conversion from string representation to enum representation. Additionally, when logging enum values, I wanted to have the enum value printed out instead of a obscure integer value. There is a drawback that I've forgotten in my initial mail, my library doesn't support white space in enum values either. As you say, it's much more simpler like this, and seemed an acceptable constraint to me.
I'm not sure the word serialization is the most appropriated in your case, as what you provide is stream I/O. Anyway the library should provide also serialization as defined in Boost.Serialization. I will add this to my to do list.
I'll have a look at what's needed for Boost.Serialization.
IIUC this is an enum that can take only the enum literals, isn't it? If yes, I suggest an alternative name: strong_enum.
Note: My library doesn't provide strong enums.
The names are probably a little bit a matter of taste (although strong enum is maybe better than static), but yes, restricting values to explicitly defined literals is the purpose of this flavor.
IIUC this is a classical enum that allow to store other values than the enum literals. I suggest to call them just enum. The specificity is associated to I/O on streams. I guess that this feature could be orthogonal to the class.
Also right, but I dont think that reusing the name enum is a good idea, it can be confusing with the native one, and "dynamic enums" are not just enums. The allowed values are in a given range, where plain enums can have any value. The specificity is associated with stream I/O, as for "static enums", as in memory, the enum variable can still take any value as the variable isn't anything more than the plain old enum itself. This is maybe not a good idea, and maybe operations should return the "unknown" value, even in-memory, if the given value is not one of the allowed ones. Additionally, when using C++11, the dynamic enums also add +,-,+= and -= operators between enum and integers, that returns an enum value. "Static enums" do only define operators between integer and enums (integer at the left hand side), that works like integer operation and automatically casts the enum value to integer. I'm not sure if it is a good idea to have the operator change its meaning whether the enum is on the lhs or rhs though...
In my library this type should correspond to an enum_set, which offer a safe interface for ordinal enums.
Ok, if enum_set is the name you're using to represent a bit field - where enum literal represent a bit position, this is it.
- No namespace support. I guess this is what the standard call scoped enums, isn't it? My library provides an emulation of scoped enums with some limitations. Some syntactic workarounds need to be used to overcome the limitations.
Scoped enums is what is used for all the flavors. In C++98, the enum is wrapped in a struct, and typedefed at namespace scope to emulate scoped enums, in C++11 enum class are used. There is no way to add enum values to namespace scope, but that was intentional and part of my requirements. What I mean here, is that the enum has to be declared in the global namespace because the macros expands to some template specialization code that will generate an error if not in the same namespace as the template declaration. To enable in-namespace enums here, I would need to know the current namespace, close it, then declare the specializations, and reopen it after. I don't think to be able to do so yet. The other way is not to have template specialization of a template that is not in the same namespace...
The syntax associated to this example
lte_enum(static_enum, lte_ev(value1,2) lte_e(value2) lte_en(value3,"value_number_3"), ::boost::uint8_t )
This is the syntax when using "shortcut" macros, which isn't enabled by default. As in the test files, I've added this functionality to have much shorter macros for the definition, but the names are much less explicit and I think it may be good for writing lighter code, but not good for comprehension. What's more understandable between "lte_en" and "TENUM_ELEMENT_NAMED"?
BOOST_ENUM_TYPE(static_enum,::boost::uint8_t, ( (value1) (2) ) ( (value2) ) ( (value3) () ("value_number_3") )
I don't know if this is more readable to you.
I don't like the overcrowding of parentheses, but that's my personal taste. We aren't doing LISP here, are we? :-)
I was looking for the Jamfile to run the test. I see now that there is a Jamroot file on the root. It would be better to provide a Jamfile for the test directory as the other Boost libraries do. I will try to run it.
The library is meant to be build separately from boost tree. The Jamroot is used as the main build file, and running bjam from the library folder should do the trick (I don't know how this works on windows though...). Best regards, Beren

Le 15/08/11 14:54, Beren Minor a écrit :
Thanks for your comments Vincente, I'll try to answer to some points.
I don't know if you know my development of Boost.Enums on the sandbox (https://svn.boost.org/svn/boost/sandbox/enums/libs/enums/doc/html/index.html). Were you aware of it? Yes, as I said, I know that there already are some enum libraries in development at Boost. I didn't know exactly how stable and how active was the development though. My point wasn't to offer another one but to get some comments on my code and on the design choices I took. I wanted to be sure you were aware of it. See my comments below about some design choices you have taken. I've had a quick look at yours, but there were some point refraining me from using it, like stream I/O, no integer implicit conversion. Stream I/O can be added to the library. Boost.Enums provides implicit conversion to integers with BOOST_ENUM_TYPE_... macros.
The library is not stable enough and doesn't takes care of stream I/O yet. Output to a stream is quite simple to add as the library provides enum to string conversion. Input from a stream will be a little more complex in the general case, but quite simple if the associated string doesn't contains white characters. I'm not sure the stream I/O of enums shouldn't use directly the string form. A manipulator could be used to this respect. I need to think about it a little more. Stream I/O was a very important requirement to me. Initially, I've coded the library to help me reading and writing key mappings from and to a configuration file. The keys were represented by enums, and could be modified with a bit field combination of modifier keys (alt, ctrl, shift...). I did want to have a straightforward conversion from string representation to enum representation. Additionally, when logging enum values, I wanted to have the enum value printed out instead of a obscure integer value. I understand the needs. There is a drawback that I've forgotten in my initial mail, my library doesn't support white space in enum values either. As you say, it's much more simpler like this, and seemed an acceptable constraint to me. Right. IIUC this is an enum that can take only the enum literals, isn't it? If yes, I suggest an alternative name: strong_enum.
Note: My library doesn't provide strong enums. The names are probably a little bit a matter of taste (although strong enum is maybe better than static), but yes, restricting values to explicitly defined literals is the purpose of this flavor. OK. I will add this kind of enums to the list of possible enhancements of my library. IIUC this is a classical enum that allow to store other values than the enum literals. I suggest to call them just enum. The specificity is associated to I/O on streams. I guess that this feature could be orthogonal to the class. Also right, but I dont think that reusing the name enum is a good idea, it can be confusing with the native one, Boost.Enums defines enum_class (no implicit conversion) and enum_type (implicit conversion) which follow the semantics of C++0x scoped enums. With this names there is no possible confussion.
and "dynamic enums" are not just enums. The allowed values are in a given range, where plain enums can have any value. The specificity is associated with stream I/O, as for "static enums", as in memory, the enum variable can still take any value as the variable isn't anything more than the plain old enum itself. This is maybe not a good idea, and maybe operations should return the "unknown" value, even in-memory, if the given value is not one of the allowed ones. Why do you need to constraint the values of the dynamic enums to a range?
Additionally, when using C++11, the dynamic enums also add +,-,+= and -= operators between enum and integers, that returns an enum value. "Static enums" do only define operators between integer and enums (integer at the left hand side), that works like integer operation and automatically casts the enum value to integer. I'm not sure if it is a good idea to have the operator change its meaning whether the enum is on the lhs or rhs though...
- No namespace support.
I guess this is what the standard call scoped enums, isn't it? My library provides an emulation of scoped enums with some limitations. Some syntactic workarounds need to be used to overcome the limitations. Scoped enums is what is used for all the flavors. In C++98, the enum is wrapped in a struct, and typedefed at namespace scope to emulate scoped enums, in C++11 enum class are used. There is no way to add enum values to namespace scope, but that was intentional and part of my requirements. What I mean here, is that the enum has to be declared in the global namespace because the macros expands to some template specialization code that will generate an error if not in the same namespace as the template declaration. To enable in-namespace enums here, I would need to know the current namespace, close it, then declare the specializations, and reopen it after. I don't think to be able to do so yet. The other way is not to have template specialization of a template that is not in the same namespace... I understand the issue now. Boost.Enums has the same problem. The syntax associated to this example
lte_enum(static_enum, lte_ev(value1,2) lte_e(value2) lte_en(value3,"value_number_3"), ::boost::uint8_t ) This is the syntax when using "shortcut" macros, which isn't enabled by default. As in the test files, I've added this functionality to have much shorter macros for the definition, but the names are much less explicit and I think it may be good for writing lighter code, but not good for comprehension. What's more understandable between "lte_en" and "TENUM_ELEMENT_NAMED"?
BOOST_ENUM_TYPE(static_enum,::boost::uint8_t, ( (value1) (2) ) ( (value2) ) ( (value3) () ("value_number_3") )
I don't know if this is more readable to you. I don't like the overcrowding of parentheses, but that's my personal taste. We aren't doing LISP here, are we? :-) No we are not doing Lisp, but macro preprocessing (provided by C/C++). Boost.Local and Boost.Contract has find a way to use some specific symbols to mean specific things. Maybe you could use the default to mean
I don't think it is a good idea to provide these operators. Enums are created explicitly from integer literals. Having the possibility to create them implicitly when doing arithmetic operations seems counter-intuitive. Boost.Enums provide a framework to define ordinal enums. With ordinal enums, you can get the position of an enum respect to the enumeration literals, and get the value associated to a position. Maybe this can respond to this need. You can also see an enum as a range, and then it has a sens to use iterators operators on the associated iterator. the next defaulted value. lte_evn(value3,default,"value_number_3"),
I was looking for the Jamfile to run the test. I see now that there is a Jamroot file on the root. It would be better to provide a Jamfile for the test directory as the other Boost libraries do. I will try to run it. The library is meant to be build separately from boost tree. The Jamroot is used as the main build file, and running bjam from the library folder should do the trick (I don't know how this works on windows though...).
Oh, I see. Best, Vicente
participants (3)
-
Beren Minor
-
Vicente Botet
-
Vicente J. Botet Escriba