Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Implementation of operators for enum class

Following the discussion in question Incrementation and decrementation of “enum class”, I'd like to ask about the possible implementation of arithmetic operators for enum class types.

Example from the original question:

enum class Colors { Black, Blue, White, END_OF_LIST };  // Special behavior for ++Colors Colors& operator++( Colors &c ) {   c = static_cast<Colors>( static_cast<int>(c) + 1 );   if ( c == Colors::END_OF_LIST )     c = Colors::Black;   return c; } 

Is there a way to implement arithmetic operators without casting to a type with already defined operators? I can't think of any, but casting bothers me. Casts are usually indication of something wrong and there has to be a very good reason for their usage. I would expect the language to allow implementation of an operator to be achievable without forcing to a specific type.

Update Dec 2018: One of the papers towards C++17 seems to address this at least partially by allowing conversions between enum class variable and the underlying type: http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2016/p0138r2.pdf

like image 966
SomeWittyUsername Avatar asked Mar 16 '13 16:03

SomeWittyUsername


People also ask

What is operator enum?

Enumeration (or enum) is a user defined data type in C. It is mainly used to assign names to integral constants, the names make a program easy to read and maintain.

What is enum operator in C++?

Enum, which is also known as enumeration, is a user-defined data type that enables you to create a new data type that has a fixed range of possible values, and the variable can select one value from the set of values.

Can enum class inherit C++?

There is no inheritance with enums. You can instead use classes with named const ints.


2 Answers

The no-cast solution is to use switch. However, you can generate a pseudo-switch using templates. The principle is to recursively process all values of the enum using a template list (or a parameter pack). So, here are 3 methods I found.

Test enum:

enum class Fruit {     apple,     banana,     orange,     pineapple,     lemon }; 

The vanilla switch (live here):

Fruit& operator++(Fruit& f) {     switch(f)     {         case Fruit::apple:     return f = Fruit::banana;         case Fruit::banana:    return f = Fruit::orange;         case Fruit::orange:    return f = Fruit::pineapple;         case Fruit::pineapple: return f = Fruit::lemon;         case Fruit::lemon:     return f = Fruit::apple;     } } 

The C++03-ish method (live here):

template<typename E, E v> struct EnumValue {     static const E value = v; };  template<typename h, typename t> struct StaticList {     typedef h head;     typedef t tail; };  template<typename list, typename first> struct CyclicHead {     typedef typename list::head item; };  template<typename first> struct CyclicHead<void,first> {     typedef first item; };  template<typename E, typename list, typename first = typename list::head> struct Advance {     typedef typename list::head lh;     typedef typename list::tail lt;     typedef typename CyclicHead<lt, first>::item next;      static void advance(E& value)     {         if(value == lh::value)             value = next::value;         else             Advance<E, typename list::tail, first>::advance(value);     } };  template<typename E, typename f> struct Advance<E,void,f> {     static void advance(E& value)     {     } };  /// Scalable way, C++03-ish typedef StaticList<EnumValue<Fruit,Fruit::apple>,         StaticList<EnumValue<Fruit,Fruit::banana>,         StaticList<EnumValue<Fruit,Fruit::orange>,         StaticList<EnumValue<Fruit,Fruit::pineapple>,         StaticList<EnumValue<Fruit,Fruit::lemon>,         void > > > > > Fruit_values;  Fruit& operator++(Fruit& f) {     Advance<Fruit, Fruit_values>::advance(f);     return f; } 

The C++11-ish method (live here):

template<typename E, E first, E head> void advanceEnum(E& v) {     if(v == head)         v = first; }  template<typename E, E first, E head, E next, E... tail> void advanceEnum(E& v) {     if(v == head)         v = next;     else         advanceEnum<E,first,next,tail...>(v); }  template<typename E, E first, E... values> struct EnumValues {     static void advance(E& v)     {         advanceEnum<E, first, first, values...>(v);     } };  /// Scalable way, C++11-ish typedef EnumValues<Fruit,         Fruit::apple,         Fruit::banana,         Fruit::orange,         Fruit::pineapple,         Fruit::lemon > Fruit_values11;  Fruit& operator++(Fruit& f) {     Fruit_values11::advance(f);     return f; } 

(C++11-ish old version)

You may be able to extend by adding some preprocessor to remove the need to repeat the list of values.

like image 189
Synxis Avatar answered Sep 22 '22 19:09

Synxis


Every operator in C++ on enums can be written without casting to an underlying type, but the result would be ridiculously verbose.

As an example:

size_t index( Colors c ) {   switch(c) {     case Colors::Black: return 0;     case Colors::Blue: return 1;     case Colors::White: return 2;   } } Color indexd_color( size_t n ) {   switch(n%3) {     case 0: return Colors::Black;     case 1: return Colors::Blue;     case 2: return Colors::White;   } } Colors increment( Colors c, size_t n = 1 ) {   return indexed_color( index(c) + n ); } Colors decrement( Colors c, size_t n = 1 ) {   return indexed_color( index(c)+3 - (n%3) ); } Colors& operator++( Colors& c ) {   c = increment(c)   return c; } Colors operator++( Colors& c, bool ) {   Colors retval = c;   c = increment(c)   return retval; } 

and a smart compiler will be able to turn these into operations that are directly on the base integral type.

But casting to a base integral type in the interface of your enum class is not a bad thing. And operators are part of the interface for your enum class.

If you don't like that loop through size_t and consider it a fake cast, you can just write:

Colors increment( Colors c ) {   switch(c) {     case Colors::Black: return Colors::Blue;     case Colors::Blue: return Colors::White;     case Colors::White: return Colors::Black;   } } 

and similarly for decrement, and implement increment-by-n as loops of repeated increment.

like image 36
Yakk - Adam Nevraumont Avatar answered Sep 21 '22 19:09

Yakk - Adam Nevraumont