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
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.
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.
There is no inheritance with enums. You can instead use classes with named const ints.
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 };
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; } }
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; }
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.
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
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With