Реализация операторов для перечислимого класса

После обсуждения вопроса Увеличение и уменьшение «enum class», Я хотел бы спросить о возможной реализации арифметических операторов для enum class типы.

Пример из оригинального вопроса:

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;
}

Есть ли способ реализовать арифметические операторы без приведения к типу с уже определенными операторами? Я не могу думать ни о чем, но кастинг беспокоит меня. Приведения типов обычно указывают на что-то неправильное, и для их использования должна быть очень веская причина. Я ожидал бы, что язык позволит реализовать оператор, не прибегая к конкретному типу.

Обновление декабря 2018:
Кажется, что одна из статей, посвященных C ++ 17, решает эту проблему, по крайней мере, частично, разрешая преобразования между переменной класса enum и базовым типом: http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2016/p0138r2.pdf

29

Решение

Решение без приведения в действие — использовать переключатель. Однако вы можете сгенерировать псевдопереключатель с помощью шаблонов. Принцип состоит в том, чтобы рекурсивно обрабатывать все значения перечисления, используя список шаблонов (или пакет параметров). Итак, вот 3 метода, которые я нашел.

Тестовое перечисление:

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

Ванильный выключатель (живи здесь):

/// Non-scalable way
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;
}
}

С ++ 03-иш метод (живи здесь):

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;
}

С ++ 11-й метод (живи здесь):

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-ти старая версия)

Вы можете расширить, добавив некоторый препроцессор, чтобы избавить от необходимости повторять список значений.

28

Другие решения

Каждый оператор в C ++ для перечислений может быть написан без приведения к базовому типу, но результат будет смехотворно многословным.

В качестве примера:

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;
}

и умный компилятор сможет превратить их в операции, которые непосредственно относятся к базовому интегральному типу.

Но приведение к базовому интегральному типу в интерфейсе вашего enum class не плохая вещь И операторы являются частью интерфейса для вашего enum class,

Если вам не нравится этот цикл size_t и считать это поддельным актером, вы можете просто написать:

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

и аналогично для декремента, и реализовывать пошаговоеn как повторяющиеся петли increment,

4

enum class Colors { Black, Blue, White };

Colors operator++(Colors& color)
{
color = (color == Colors::White) ? Colors::Black : Colors(int(color) + 1);
return color;
}

Проверьте в C ++ Shell

-1
По вопросам рекламы ammmcru@yandex.ru
Adblock
detector