2016-04-22 6 views
3

Мне нужен общий класс шаблона без макросов магии, который можно использовать, как это:Как создать оператор шаблона для перечислений

template<typename E> 
class enum_operators 
{ 
    E& operator++(E& orig) 
    { 
     orig = static_cast<E>(orig + 1); 
     return orig; 
    } 
}; 

enum colors : public enum_operators<colors> 
{ 
    white, 
    red, 
    green, 
    blue 
}; 

enum corners : public enum_operators<corners> 
{ 
    topleft, 
    topright, 
    bottomleft, 
    bottomright 
}; 

Можно ли с VARIADIC шаблон или что-то еще? Как я могу это сделать?

+0

Нужно ли вам наследование класса? Не можете ли вы определить оператора вне класса? – thorsan

+2

Проблема будет заключаться в определении количества элементов в перечислении, пропущенных значений и т. Д. Вы не можете увеличить число переходов в стиле C. – JvO

ответ

4

Строительство на 101010-х и wowofbob 's ответы:

template <class E, class = std::enable_if_t<std::is_enum<E>{}>> 
E &operator ++ (E &e) { 
    return e = static_cast<E>(
     static_cast<std::underlying_type_t<E>>(e) + 1 
    ); 
} 

Я SFINAE'd прочь оператору за все, что не является перечисление, и добавил надлежащего static_cast ИНГ так что он работает на enum class es тоже.

Live on Coliru

В случае, если вы не хотите, чтобы предоставить все enum S под солнцем способность увеличиваться, вы можете вложить этот оператор в пространстве имен (скажем ::incr_enum), а затем вы можете:

  • Объявите свои перечисления в этом пространстве имен, как описано в 101010, и в этом случае оператор найден через ADL;
  • using namespace incr_enum;, когда вы хотите импортировать и использовать этот оператор в локальной области.
+1

Я думаю, что это очень важно (я просто набрал все это), ограничивая область действия шаблона 'operator ++'. Если OP ** действительно ** хочет/нуждается в общем приращении перечисления, OP должен будет ограничить это как можно больше. Например, 'std :: real_ops', вводящие общие операторы могут быть полезны, но если их использовать слишком тяжело, это может также вызвать хаос. – Niall

+0

@Niall Я был на заборе об загрязнении пространства имен, поскольку это ограничивается перечислением. Но не стоит и упоминать об этом. – Quentin

+0

Он выглядит великолепно, но, к сожалению, он не работает в VS2015. У меня есть эта ошибка: 1> g: \ work \ enumtest \ consoleapplication1 \ main.cpp (21): ошибка C2783: 'E & operator ++ (E &)': не удалось вывести аргумент шаблона для ' ' 1> g: \ work \ enumtest \ consoleapplication1 \ main.cpp (15): note: см. Объявление' operator ++ ' 1> g: \ work \ enumtest \ consoleapplication1 \ main.cpp (21): ошибка C2675: unary '++': «Enum» не определяет этот оператор или преобразование в тип, приемлемый для предопределенного оператора – csbako

0

Вам не нужны классы здесь.

Это прекрасно работает:

#include <assert.h> 

enum colors 
{ 
    white, 
    red, 
    green, 
    blue 
}; 

enum corners 
{ 
    topleft, 
    topright, 
    bottomleft, 
    bottomright 
}; 


template<class Enum> 
Enum next(Enum e) { 
    // To cast back to Enum from `int` 
    return static_cast<Enum>(e + 1); 
} 

int main() { 

    colors c = white; 
    colors d = next(c); 
    assert(c == white); 
    assert(d == red); 

} 
2

Вы не можете наследовать class от enum или enum class. По моему скромному мнению, самое лучшее, что вы можете сделать, это определить ваш перегруженный шаблоном оператор как бесплатную функцию и поместить его и все из enum s, с которыми вы хотите работать с ним в пространстве имен (например, fancy), и пусть поиск по имени сделает все остальное:

namespace fancy { 

enum colors { white, red, green, blue }; 
enum corners { topleft, topright, bottomleft, bottomright }; 

template<typename E> 
E& operator++(E &e) { 
    e = static_cast<E>(static_cast<int>(e) + 1); 
    return e; 
} 

} // end of namespace fancy 

Таким образом, вы ограничите работу своего оператора только тем, что у вас есть в вашем пространстве имен.

Live Demo

+1

Это, конечно, предполагает, что базовые значения перечисления являются последовательными и без пробелов. Это также не мешает вам увеличивать время до конца перечисления. Вам нужно будет вставить значение защиты END_OF_ENUM в качестве последнего перечислимого значения, а затем вручную проверить, соответствует ли ваше значение с расширением enum> =. – Fibbles

+0

@Fibbles да, строки прилагаются. – 101010

1

Не используйте operator ++! Что вы должны делать, если у вас есть перечисление как это ?:

enum class other_enum : int 
{ 
    low = -3000, 
    fabada_asturiana = 0xfabada, 
    answer_to_life_universe_everything = 0b101010, 
    high = -low 
}; 

Как вы можете видеть, значения не достижимы увеличения одной предыдущей и даже не имеют рисунок; вместо этого используйте итераторы. На основе this answer:

// Shortcut to the enum map. 
template <typename ENUM> 
using enum_map = std::map<ENUM, const std::string>; 

// The enum map. 
template <typename ENUM> 
enum_map<ENUM> enum_values{}; 

// Empty function to end the recursion. 
void initialize() {} 

// Initialize the enum map. 
template <typename ENUM, typename ... args> 
void initialize(const ENUM value, const char *name, args ... tail) 
{ 
    enum_values<ENUM>.emplace(value, name); 
    initialize(tail ...); 
} 

// Obtain the begin iterator to the enum 
template <class ENUM, class = std::enable_if_t<std::is_enum<ENUM>{}>> 
auto begin(ENUM &) 
{ 
    return enum_values<ENUM>.begin(); 
} 

// Obtain the end iterator to the enum 
template <class ENUM, class = std::enable_if_t<std::is_enum<ENUM>{}>> 
auto end(ENUM &) 
{ 
    return enum_values<ENUM>.end(); 
} 

Код выше позволяет пользователю создать карту со значениями перечислений и строки описания, он может быть использован как это:

int main() 
{ 
    initialize 
    (
     white, "White", 
     red, "Red", 
     green, "Green", 
     blue, "Blue", 

     topleft,  "Top Left", 
     topright, "Top Right", 
     bottomleft, "Bottom Left", 
     bottomright, "Bottom Right", 

     other_enum::low, "Lowest value", 
     other_enum::fabada_asturiana, "Typical Spanish", 
     other_enum::answer_to_life_universe_everything, "42", 
     other_enum::high, "Higher value" 
    ); 

    ... 
    return 0; 
} 

Но initialize вызов является обязательным чтобы все это работало; не требуется, чтобы все перечислить в один вызов.

При всем коде выше, мы можем перебирать перечислений таким образом (Live demo):

for (const auto &v : colors{}) 
    std::cout << v.first << '\n'; // Print values 
for (const auto &v : corners{}) 
    std::cout << v.second << '\n'; // Print names 
for (const auto &v : other_enum{}) 
    std::cout << (int)v.first << " = " << v.second << '\n'; // Print values & names 

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

-1
namespace enum_operator 
{ 
    template<typename E> 
    E& operator++(E& e) 
    { 
     e = static_cast<E>(static_cast<int>(e) + 1); 
     return e; 
    } 
}; 

namespace 
{ 
    enum colors 
    { 
     white, 
     red, 
     green, 
     blue 
    }; 

    enum corners 
    { 
     topleft, 
     topright, 
     bottomleft, 
     bottomright 
    }; 

    using namespace enum_operator 
}; 
+1

В этом ответе можно использовать некоторое объяснение, в частности, почему используется анонимное пространство имен. – Niall

+2

Добро пожаловать в переполнение стека! Хотя этот код может ответить на вопрос, предоставление дополнительного контекста относительно того, почему и/или как он отвечает на вопрос, значительно улучшит его долгосрочную ценность. Пожалуйста, отредактируйте свой ответ, чтобы добавить какое-то объяснение. – CodeMouse92

0
namespace operator_support { 
    template<class T> struct tag_t{using type=T; constexpr tag_t(){}}; 
    template<class T> constexpr tag_t<T> tag = {}; 

    template<class T> 
    std::false_type operator_support::supports_plus_plus(tag_t<T>); 

    template<class E> 
    decltype(supports_plus_plus(tag<E>)) supports_plus_plus_v = {}; 
} 

Для того, чтобы сделать ваше перечисление supports_plus_plus_t<E> быть true_type, просто написать std::true_type supports_plus_plus< tag_t<E> >(); в том же пространстве имен вашего перечисления E. ADL делает все остальное.

namespace plus_plus { 
    template <class E, 
    std::enable_if_t<operator_support::supports_plus_plus_v<E>>* =nullptr 
    > 
    E &operator ++ (E &e) { 
    return e = static_cast<E>(
     static_cast<std::underlying_type_t<E>>(e) + 1 
    ); 
    } 
} 

для того, чтобы использовать ++ Вы должны:

  • Тип std::true_type supports_plus_plus< tag_t<E> >(); в пространстве имен перечислимого E.

  • using namespace ::plus_plus;, где вы хотите позвонить ++ на номер enum. Это ограничивает использование ++ перечислениям, которые поддерживают его, и затрагивают проблемы поиска оператора. (тело ++ от @csbako).

Вы можете сделать макрос:

#define PLUS_PLUS_POWERS_ACTIVATE(...) \ 
    std::true_type supports_plus_plus< tag_t<__VA_ARGS__> >(); \ 
    using namespace ::plus_plus 

который принимает имя перечисления (я использую в арг, как ++ имена перечислений C могут иметь встроенные , сек между <> с, что макросами не нравятся) ,

enum whatever { 
    a,b,c 
}; 
PLUS_PLUS_POWERS_ACTIVATE(whatever); 
int main() { 
    whatever x = a; 
    std::cout << x; 
    ++x; 
    std::cout << x; 
    ++x; 
    std::cout << x; 
} 

 Смежные вопросы

  • Нет связанных вопросов^_^