Typesafe enums proposal

Hello, all. I want to propose typesafe enums. Sources are available in Boost Sandbox (typesafe_enums.zip). Were tested under VC 7.1. Main features are: - type safety; - meaningful printable names; - ability to use in switch-case constructs; - iteration through enumerators; - enumerator-specific methods; Basic usage =========== class season_policy: public boost::simple_enumeration_policy { public: BOOST_DEFINE_ENUMERATOR( WINTER ) BOOST_DEFINE_ENUMERATOR( SPRING ) BOOST_DEFINE_ENUMERATOR( SUMMER ) BOOST_DEFINE_ENUMERATOR( AUTUMN ) typedef boost::mpl::vector< WINTER, SPRING, SUMMER, AUTUMN > enumerators; }; typedef boost::enumeration< season_policy > season; Type safety ----------- season s = season::WINTER(); if ( s ) // accidentally forgotten comparison, wouldn't compile BOOST_ASSERT( false ); if ( s == season::SUMMER() ) std::cout << "Let's go in for fishing!"; Meaningful printable names -------------------------- season s = season::WINTER(); BOOST_CHECK_EQUAL( s->get_name(), "WINTER" ); Ability to use in switch-case constructs ---------------------------------------- season s = season::SUMMER(); switch ( s.get_id() ) { case BOOST_ENUMERATOR_ID( season, SUMMER ): break; default: BOOST_ERROR( "Should not fall here." ); break; } Iteration through enumerators ----------------------------- for ( season::iterator i = season::begin(); i != season::end(); ++i ) std::cout << ( *i )->get_name() << " "; Advanced usage ============== class operation_policy { public: class base_enumerator { public: virtual double eval( double operand1, double operand2 ) const = 0; }; template < typename Functor > class enumerator_impl: public base_enumerator { public: double eval( double operand1, double operand2 ) const { return Functor()( operand1, operand2 ); } }; class PLUS: public enumerator_impl< std::plus< double > > { }; class MINUS: public enumerator_impl< std::minus< double > > { }; class TIMES: public enumerator_impl< std::multiplies< double > > { }; class DIVIDE: public enumerator_impl< std::divides< double > > { }; typedef boost::mpl::vector< PLUS, MINUS, TIMES, DIVIDE > enumerators; }; typedef boost::enumeration< operation_policy > operation; Enumerator-specific methods --------------------------- for ( season::iterator i = season::begin(); i != season::end(); ++i ) std::cout << ( *i )->eval( 4, 2 ) << " "; // expected output is: 6 2 8 2 Is anybody found it useful? Sincerely, Maksym Motornyy.

Maksym Motornyy wrote: Is there a way to integrate a solution to this problem : [google groups] http://tinyurl.com/dtnx3 -seb
Hello, all.
I want to propose typesafe enums. Sources are available in Boost Sandbox (typesafe_enums.zip). Were tested under VC 7.1.
Main features are: - type safety; - meaningful printable names; - ability to use in switch-case constructs; - iteration through enumerators; - enumerator-specific methods;
Basic usage ===========
class season_policy: public boost::simple_enumeration_policy { public: BOOST_DEFINE_ENUMERATOR( WINTER ) BOOST_DEFINE_ENUMERATOR( SPRING ) BOOST_DEFINE_ENUMERATOR( SUMMER ) BOOST_DEFINE_ENUMERATOR( AUTUMN )
typedef boost::mpl::vector< WINTER, SPRING, SUMMER, AUTUMN > enumerators; };
typedef boost::enumeration< season_policy > season;
Type safety -----------
season s = season::WINTER();
if ( s ) // accidentally forgotten comparison, wouldn't compile BOOST_ASSERT( false );
if ( s == season::SUMMER() ) std::cout << "Let's go in for fishing!";
Meaningful printable names --------------------------
season s = season::WINTER(); BOOST_CHECK_EQUAL( s->get_name(), "WINTER" );
Ability to use in switch-case constructs ----------------------------------------
season s = season::SUMMER();
switch ( s.get_id() ) { case BOOST_ENUMERATOR_ID( season, SUMMER ): break; default: BOOST_ERROR( "Should not fall here." ); break; }
Iteration through enumerators -----------------------------
for ( season::iterator i = season::begin(); i != season::end(); ++i ) std::cout << ( *i )->get_name() << " ";
Advanced usage ==============
class operation_policy { public: class base_enumerator { public: virtual double eval( double operand1, double operand2 ) const = 0; };
template < typename Functor > class enumerator_impl: public base_enumerator { public: double eval( double operand1, double operand2 ) const { return Functor()( operand1, operand2 ); } };
class PLUS: public enumerator_impl< std::plus< double > > { };
class MINUS: public enumerator_impl< std::minus< double > > { };
class TIMES: public enumerator_impl< std::multiplies< double > > { };
class DIVIDE: public enumerator_impl< std::divides< double > > { };
typedef boost::mpl::vector< PLUS, MINUS, TIMES, DIVIDE > enumerators; };
typedef boost::enumeration< operation_policy > operation;
Enumerator-specific methods ---------------------------
for ( season::iterator i = season::begin(); i != season::end(); ++i ) std::cout << ( *i )->eval( 4, 2 ) << " "; // expected output is: 6 2 8 2
Is anybody found it useful?
Sincerely, Maksym Motornyy.
_______________________________________________ Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost

Sebastien Martel wrote:
Is there a way to integrate a solution to this problem :
[google groups] http://tinyurl.com/dtnx3
-seb
Yes, that's actually enumerator-specific methods are what for. Here is an example how it can be done with my library. class part_of_speech_policy { public: class base_enumerator { public: virtual const char *get_encoding() const = 0; }; class coordinating_conjunction: public base_enumerator { public: const char *get_encoding() const { return "CC"; } }; class cardinal_number: public base_enumerator { public: const char *get_encoding() const { return "CD"; } }; class determiner: public base_enumerator { public: const char *get_encoding() const { return "DT"; } }; typedef boost::mpl::vector< coordinating_conjunction , cardinal_number , determiner > enumerators; }; typedef boost::enumeration< part_of_speech_policy > part_of_speech; // somewhere in other place BOOST_STATIC_ASSERT( part_of_speech::size == 3 ); part_of_speech foo = part_of_speech::coordinating_conjunction(); BOOST_CHECK_EQUAL( foo->get_encoding(), "CC" ); Hope it does what you expected. Sincerely yours, Maksym Motornyy.

On 06/17/2005 10:47 AM, Maksym Motornyy wrote:
Hello, all.
I want to propose typesafe enums. Sources are available in Boost Sandbox (typesafe_enums.zip). Were tested under VC 7.1.
Main features are: - type safety; - meaningful printable names; I find this very useful. - ability to use in switch-case constructs; - iteration through enumerators; Something similar has been proposed on the MPL to do list:
http://www.crystalclearsoftware.com/cgi-bin/boost_wiki/wiki.pl?MPL_TODO_List search for "don't work with enumeration types:" Example code is at: http://boost-sandbox.sourceforge.net/vault/index.php?&direction=0&order=&directory=cppljevans/mpl in range_all.zip. If your enumerators can be used to make tuples or variants with "named" fields, as done in the: cppljevans/tuple_nested_enum_test.cpp in the vault, I'd be interested.

Something similar has been proposed on the MPL to do list:
http://www.crystalclearsoftware.com/cgi-bin/boost_wiki/wiki.pl?MPL_TODO_List
search for "don't work with enumeration types:"
Since enumerators in my library are plain classes there is no problem to use them with MPL. Even more, mpl::vector is used to define their sequence order (policy::enumerators typedef).
in range_all.zip. If your enumerators can be used to make tuples or variants with "named" fields, as done in the:
cppljevans/tuple_nested_enum_test.cpp
in the vault, I'd be interested.
I can't swear I understand what is your goal. The test code is a bit hard to read. Could you explain, please? Sincerely yours, Maksym Motornyy.

On 06/20/2005 04:25 AM, Maksym Motornyy wrote: [snip]
search for "don't work with enumeration types:"
Since enumerators in my library are plain classes there is no problem to use them with MPL. Even more, mpl::vector is used to define their sequence order (policy::enumerators typedef).
Hmm... This sounds like it might work for me. I'll take a closer look.
in range_all.zip. If your enumerators can be used to make tuples or variants with "named" fields, as done in the:
cppljevans/tuple_nested_enum_test.cpp
in the vault, I'd be interested.
I can't swear I understand what is your goal. The test code is a bit hard to read. Could you explain, please?
^^^^^^^^^^^^ Sorry about that. Is it hard to read because of: a) the formatting or: b) lack of commenting or: c) use of template metaprogramming or all three? Of course I understand it needs more comments, but it was uploaded in order to reference from: http://groups-beta.google.com/group/comp.std.c++/msg/7d7d19cf16333d0b?hl=en where the first paragraph of my reponse hopefully makes the *goal* a little clearer.

http://groups-beta.google.com/group/comp.std.c++/msg/7d7d19cf16333d0b?hl=en
The main reason why it was hard to read your code is my lack of knowledge what are you trying to implement. This link clarifies a lot. Second, I wonder why somebody wants to have named fields in tuples? Isn't tuples designed to be anonymous inline-declarable structs? Otherwise it's better to use plain struct's (of course I can be wrong). Sincerely yours, Maksym Motornyy.

http://groups-beta.google.com/group/comp.std.c++/msg/7d7d19cf16333d0b?hl=en
The main reason why it was hard to read your code is my lack of knowledge what are you trying to implement. This link clarifies a lot. Great!
Second, I wonder why somebody wants to have named fields in tuples? Isn't tuples designed to be anonymous inline-declarable structs? Otherwise it's better to use plain struct's (of course I can be wrong). Well, there you made me think again. I just believed names would be better than indices and that belief was supported by some post to some mailing list or newsgroup (I thought it was the loki mailing
On 06/20/2005 07:08 AM, Maksym Motornyy wrote: list at http://sourceforge.net/mail/?group_id=29557 ; however, I can't find it now). So I searched some more and all I found was: http://groups-beta.google.com/group/comp.lang.c++.moderated/msg/d318ecb3ce57... where the phrase: "cannot be accessible by name, only by index" suggests the author also believes that named access is better than indexed access. I also found: http://www-ti.informatik.uni-tuebingen.de/~weissr/doc/gilf_3.4.2003.pdf.gz which, in section A.1.2, contains: named index for every property which suggests the same as the previous citation. I had in mind something like the enumerations could represent field names in a database record, and the corresponding typelist would be the types of those fields. I was thinking that then writing some relational algebra mpl classes would then be easier with named indices rather than numbered indices. I was also thinking of how you would understandably describe such a database schema without using names (i.e. enumerators like "first_name", "last_name" "social_security_number") rather than numerals (0,1,2). Anyway, I hadn't carefully thought it out, I'd just jumped to the conclusion that names were better, although after some research on the topic (which, sadly, I can't reproduce at the moment. I know I had the loki maillist reference bookmarked at one time, but it's disappeared). HTH.

----- Original Message ----- From: "Maksym Motornyy" <mmotorny@yandex.ru> To: <boost@lists.boost.org> Sent: Friday, June 17, 2005 11:47 AM Subject: [boost] Typesafe enums proposal
Hello, all.
I want to propose typesafe enums. Sources are available in Boost Sandbox (typesafe_enums.zip). Were tested under VC 7.1.
Thank you for sharing your code.
Main features are: - type safety; - meaningful printable names;
I think that the names are only "meaningful" if the source language and the application user's language are the same. In general this kind of ability is useful in debugging but in real applications the restraints on identifiers is too limiting (i.e. no punctuation, no accents, no spaces). Far more powerful would be the ability to associate arbitrary data with an enum. This I believe is relatively easy to add to your framework. [snip
Is anybody found it useful?
Yes I think it would be useful.
Sincerely, Maksym Motornyy.
I wish to state for the record, I believe macros should only be used as a last resort. I agree that they make certain tasks more convenient but the following coding pardadigm for implementing enums works almost as well, except that it lacks "meaningful names": template<typename T, int N> struct enumerator { const static int value = N; typedef enumerator<T, N + 1> next; }; struct weekday { // self typedef typedef weekday self; // 'ctors template<int N> weekday(const enumerator<self, N>& x) : value(N) { } weekday(const self& x) : value(x.value) { } // enumerations typedef enumerator<self, 0> mon; typedef mon::next tue; typedef tue::next wed; typedef wed::next thu; typedef thu::next fri; typedef fri::next sat; typedef sat::next sun; typedef sun::next end_day; // operators self& operator++(int) { ++value; return *this; } self& operator--(int) { --value; return *this; } self operator++() { self tmp = *this; ++value; return tmp; } self operator--() { self tmp = *this; --value; return tmp; } friend bool operator==(const weekday& x, const weekday& y) { return x.value == y.value; } friend bool operator!=(const weekday& x, const weekday& y) { return x.value != y.value; } operator int() const { return value; } // static functions static mon begin() { return mon(); } static end_day end() { return end_day(); } // fields int value; }; void weekday_reaction(const weekday& x) { switch (x) { case (weekday::mon::value) : std::cout << "doh! monday" << std::endl; break; case (weekday::tue::value) : std::cout << "doh! tuesday" << std::endl; break; case (weekday::wed::value) : std::cout << "doh! hump-day" << std::endl; break; case (weekday::thu::value) : std::cout << "woohoo! day before friday" << std::endl; break; case (weekday::fri::value) : std::cout << "woohoo! friday" << std::endl; break; case (weekday::sat::value) : std::cout << "woohoo! saturday" << std::endl; break; case (weekday::sun::value) : std::cout << "doh! church!" << std::endl; break; default: std::cout << "whatcha talkin' 'bout willis?" << std::endl; break; } } int main() { weekday_reaction(weekday::sat()); // weekday_reaction(42); does not compile weekday_reaction(weekday::end()); for (weekday wd = weekday::begin(); wd != weekday::end(); wd++) { weekday_reaction(wd); } system("pause"); return 0; } Comparing this to your macro technique, using macros is much more compact and convenient. I for one would not use macros for enums, but I think your library has potential: Christopher Diggins http://www.cdiggins.com

I think that the names are only "meaningful" if the source language and the application user's language are the same. In general this kind of ability is useful in debugging but in real applications the restraints on identifiers is too limiting (i.e. no punctuation, no accents, no spaces). Far more powerful would be the ability to associate arbitrary data with an enum. This I believe is relatively easy to add to your framework.
Yes, mainly the "meaningful" names can be used for debugging and logging. As I wrote in above reply to Sebastien Martel, arbitary data can be added by means of enumerator-specific methods.
I wish to state for the record, I believe macros should only be used as a last resort.
Absolutely agree. If you find "meaningful" names not very useful feature, enumerator definition can be done without macros. Sincerely yours, Maksym Motornyy.

From: Maksym Motornyy <mmotorny@yandex.ru>
I think that the names are only "meaningful" if the source language and the application user's language are the same. In general this kind of ability is useful in debugging but in real applications the restraints on identifiers is too limiting (i.e. no punctuation, no accents, no spaces). Far more powerful would be the ability to associate arbitrary data with an enum. This I believe is relatively easy to add to your framework.
Yes, mainly the "meaningful" names can be used for debugging and logging. As I wrote in above reply to Sebastien Martel, arbitary data can be added by means of enumerator-specific methods.
We write real applications, but we don't need i18n support in them as they are for internal use. We have a better enum mechanism, though it relies on macros to generate the necessary code. Though that mechanism, we frequently turn enumerators into strings (in both long and short forms) and turn strings into enumerators.
I wish to state for the record, I believe macros should only be used as a last resort.
Here's our syntax: SA_BEGIN_ENUM(example) enumerator1[ = expression], enumerator2[ = expression], ... enumeratorN[ = expression] SA_END_ENUM(example) The result is, effectively: class example { public: enum value { enumerator1[ = expression], enumerator2[ = expression], ... enumeratorN[ = expression] }; example() { } example(value value_i) : value_(value_i) { } operator value() const { return value_; } std::string to_string(bool verbose_i = false) const { return map_s.get_name(value_, verbose_i); } static example from_string(std::string const & name_i, example not_found_i) { return static_cast<value>(map_s.get_value(name_i, not_found_i)); } static example from_string(std::string const & name_i) // throws an exception { return static_cast<value>(map_s.get_value(name_i)); } static void dump() { map_s.dump(); } // define various operators private: static ::saol::details::enum_name_mapper map_s; value value_; public: static void register_enumerators(); }; template <typename Stream> Stream & operator <<(Stream & stream_io, example value_i) { return stream_io << value_i.to_string(); } To set the names, we use SA_BEGIN_ENUMERATOR_REGISTRATION(example) SA_REGISTER_ENUMERATOR(enumerator1, "Some long description") SA_REGISTER_SIMPLE_ENUMERATOR(enumerator1) ... SA_END_ENUMERATOR_REGISTRATION(example) SA_REGISTER_ENUMERATOR registers the enumerator name converted to a string (preprocessor stringizing) plus the long description as the non-verbose and verbose forms, respectively. SA_REGISTER_SIMPLE_ENUMERATOR registers the enumerator name as both the long and short forms.
Absolutely agree. If you find "meaningful" names not very useful feature, enumerator definition can be done without macros.
We use that regularly. Since the macros generate typesafe code, we don't find their use problematic. -- Rob Stewart stewart@sig.com Software Engineer http://www.sig.com Susquehanna International Group, LLP using std::disclaimer;

To set the names, we use
SA_BEGIN_ENUMERATOR_REGISTRATION(example) SA_REGISTER_ENUMERATOR(enumerator1, "Some long description") SA_REGISTER_SIMPLE_ENUMERATOR(enumerator1) ... SA_END_ENUMERATOR_REGISTRATION(example) Did you look at my library? You can register with enumerators not only short and verbose names but also arbitrary data (example of such data is expression on the right of enumerator).
We use that regularly. Since the macros generate typesafe code, we don't find their use problematic.
For me using macro is also not a problem. If one finds macros ugly he can declare enumerator in plain C++. Sincerely, Maksym Motornyy.
participants (5)
-
christopher diggins
-
Larry Evans
-
Maksym Motornyy
-
Rob Stewart
-
Sebastien Martel