Перечислять перечисления в C++

В C++ возможно ли перечислить по enum (либо во время выполнения, либо во время компиляции (желательно)) и вызывать функции / генерировать код для каждой итерации?

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

enum abc
{    
    start
    a,
    b,
    c,
    end
}    
for each (__enum__member__ in abc)
{    
    function_call(__enum__member__);    
}

Возможные дубликаты:

9 ответов

Решение

Чтобы добавить в ответ @StackedCrooked, вы можете перегрузить operator++, operator-- а также operator* и иметь итератор, как функциональность.

enum Color {
    Color_Begin,
    Color_Red = Color_Begin,
    Color_Orange,
    Color_Yellow,
    Color_Green,
    Color_Blue,
    Color_Indigo,
    Color_Violet,
    Color_End
};

namespace std {
template<>
struct iterator_traits<Color>  {
  typedef Color  value_type;
  typedef int    difference_type;
  typedef Color *pointer;
  typedef Color &reference;
  typedef std::bidirectional_iterator_tag
    iterator_category;
};
}

Color &operator++(Color &c) {
  assert(c != Color_End);
  c = static_cast<Color>(c + 1);
  return c;
}

Color operator++(Color &c, int) {
  assert(c != Color_End); 
  ++c;
  return static_cast<Color>(c - 1);
}

Color &operator--(Color &c) {
  assert(c != Color_Begin);
  return c = static_cast<Color>(c - 1);
}

Color operator--(Color &c, int) {
  assert(c != Color_Begin); 
  --c;
  return static_cast<Color>(c + 1);
}

Color operator*(Color c) {
  assert(c != Color_End);
  return c;
}

Давайте проверим с некоторыми <algorithm> шаблон

void print(Color c) {
  std::cout << c << std::endl;
}

int main() {
  std::for_each(Color_Begin, Color_End, &print);
}

Сейчас, Color постоянный двунаправленный итератор. Вот класс многократного использования, который я кодировал, делая это вручную выше. Я заметил, что это может работать для многих других перечислений, поэтому повторять один и тот же код снова довольно утомительно

// Code for testing enum_iterator
// --------------------------------

namespace color_test {
enum Color {
  Color_Begin,
  Color_Red = Color_Begin,
  Color_Orange,
  Color_Yellow,
  Color_Green,
  Color_Blue,
  Color_Indigo,
  Color_Violet,
  Color_End
};

Color begin(enum_identity<Color>) {
  return Color_Begin;
}

Color end(enum_identity<Color>) {
  return Color_End;
}
}

void print(color_test::Color c) {
  std::cout << c << std::endl;
}

int main() {
  enum_iterator<color_test::Color> b = color_test::Color_Begin, e;
  while(b != e)
    print(*b++);
}

Реализация следует.

template<typename T>
struct enum_identity { 
  typedef T type; 
};

namespace details {
void begin();
void end();
}

template<typename Enum>
struct enum_iterator 
  : std::iterator<std::bidirectional_iterator_tag, 
                  Enum> {
  enum_iterator():c(end()) { }

  enum_iterator(Enum c):c(c) { 
    assert(c >= begin() && c <= end());
  }

  enum_iterator &operator=(Enum c) {
    assert(c >= begin() && c <= end());
    this->c = c; 
    return *this;
  }

  static Enum begin() {
    using details::begin; // re-enable ADL
    return begin(enum_identity<Enum>());
  }

  static Enum end() {
    using details::end; // re-enable ADL
    return end(enum_identity<Enum>());
  }

  enum_iterator &operator++() {
    assert(c != end() && "incrementing past end?");
    c = static_cast<Enum>(c + 1);
    return *this;
  }

  enum_iterator operator++(int) {
    assert(c != end() && "incrementing past end?");
    enum_iterator cpy(*this);
    ++*this;
    return cpy;
  }

  enum_iterator &operator--() {
    assert(c != begin() && "decrementing beyond begin?");
    c = static_cast<Enum>(c - 1);
    return *this;
  }

  enum_iterator operator--(int) {
    assert(c != begin() && "decrementing beyond begin?");
    enum_iterator cpy(*this);
    --*this;
    return cpy;
  }

  Enum operator*() {
    assert(c != end() && "cannot dereference end iterator");
    return c;
  }

  Enum get_enum() const {
    return c;
  }

private:
  Enum c;
};

template<typename Enum>
bool operator==(enum_iterator<Enum> e1, enum_iterator<Enum> e2) {
  return e1.get_enum() == e2.get_enum();
}

template<typename Enum>
bool operator!=(enum_iterator<Enum> e1, enum_iterator<Enum> e2) {
  return !(e1 == e2);
}

C++ в настоящее время не обеспечивает итерацию перечислителя. Несмотря на это, иногда возникает необходимость в этом. Обычный обходной путь - добавить значения, которые отмечают начало и конец. Например:

enum Color
{
    Color_Begin,
    Color_Red = Color_Begin,
    Color_Orange,
    Color_Yellow,
    Color_Green,
    Color_Blue,
    Color_Indigo,
    Color_Violet,
    Color_End
};

void foo(Color c)
{
}


void iterateColors()
{
    for (size_t colorIdx = Color_Begin; colorIdx != Color_End; ++colorIdx)
    {
        foo(static_cast<Color>(colorIdx));
    }
}

Ни один не возможен без небольшого ручного труда. Большую часть работы можно выполнить с помощью макросов, если вы хотите углубиться в эту область.

Расширяя сказанное Конрадом, одна возможная идиома в случае "генерации кода для каждой итерации" состоит в использовании включенного файла для представления перечисления:

mystuff.h:

#ifndef LAST_ENUM_ELEMENT
#define LAST_ENUM_ELEMENT(ARG) ENUM_ELEMENT(ARG)
#endif

ENUM_ELEMENT(foo)
ENUM_ELEMENT(bar)
LAST_ENUM_ELEMENT(baz)

// not essential, but most likely every "caller" should do it anyway...
#undef LAST_ENUM_ELEMENT
#undef ENUM_ELEMENT

enum.h:

// include guard goes here (but mystuff.h doesn't have one)

enum element {
    #define ENUM_ELEMENT(ARG) ARG,
    #define LAST_ENUM_ELEMENT(ARG) ARG
    #include "mystuff.h"
}

main.cpp:

#include "enum.h"
#define ENUM_ELEMENT(ARG) void do_##ARG();
#include "mystuff.h"

element value = getValue();
switch(value) {
    #define ENUM_ELEMENT(ARG) case ARG: do_##ARG(); break;
    #include "mystuff.h"
    default: std::terminate();
}

Итак, чтобы добавить новый элемент "qux", вы добавляете его в mystuff.h и пишете do_qux функция. Вам не нужно трогать код отправки.

Конечно, если значения в вашем перечислении должны быть конкретными непоследовательными целыми числами, тогда вы в конечном итоге сохраните определение перечисления и ENUM_ELEMENT(foo)... список отдельно, что грязно.

нет

Однако вы можете определить свой собственный класс, который реализует перечислимые функции с итерациями. Вы можете вспомнить уловку из предыдущих 1.5 Java-дней, называемую "типовой шаблон проектирования enum". Вы могли бы сделать эквивалент C++.

Это кажется мне хакерским, но может подойти вашим целям:

enum Blah {
  FOO,
  BAR,
  NUM_BLAHS
};

// later on
for (int i = 0; i < NUM_BLAHS; ++i) {
  switch (i) {
  case FOO:
    // foo stuff
    break;
  case BAR:
    // bar stuff
    break;
  default:
    // you're missing a case statement
  }
}

Если вам нужно специальное начальное значение, вы можете сделать его константой и установить его в своем перечислении. Я не проверял, компилируется ли это, но это должно быть близко к тому, чтобы быть там:-). Надеюсь это поможет.

Я думаю, что этот подход может быть хорошим балансом для вашего варианта использования. Используйте его, если вам не нужно делать это для нескольких различных перечислимых типов, и вы не хотите иметь дело с препроцессором. Просто убедитесь, что вы прокомментируете и, возможно, добавите TODO, чтобы позже изменить его на что-то лучшее:-).

Я обычно делаю это так:

enum abc
{    
    abc_begin,
    a = abc_begin,
    b,
    c,
    abc_end
};

void foo()
{
    for( auto&& r : range(abc_begin,abc_end) )
    {
        cout << r;
    }
}


range является полностью общим и определяется следующим образом:

template <typename T>
class Range
{
public:
    Range( const T& beg, const T& end ) : b(beg), e(end) {}
    struct iterator
    {
        T val;
        T operator*() { return val; }
        iterator& operator++() { val = (T)( 1+val ); return *this; }
        bool operator!=(const iterator& i2) { return val != i2.val; }
    };
    iterator begin() const { return{b}; }
    iterator end() const { return{e}; }
private:
    const T& b;
    const T& e;
};

template <typename T>
Range<T> range( const T& beg, const T& end ) { return Range<T>(beg,end); }

Люблю шаблоны, но я собираюсь принять это к сведению для моего будущего / использования другими людьми, чтобы мы не потеряли ни одно из вышеперечисленного.

Перечисления удобны для сравнения вещей в известной упорядоченной форме. Они обычно используются жестко закодированными в функции для удобства чтения целых значений. Несколько похоже на определения препроцессора, за исключением того, что они не заменяются литералами, а хранятся и доступны во время выполнения.

Если бы у нас был enum, определяющий html коды ошибок, и мы знали, что коды ошибок в 500 - это ошибки сервера, было бы лучше прочитать что-то вроде:

enum HtmlCodes {CONTINUE_CODE=100,CLIENT_ERROR=400,SERVER_ERROR=500,NON_STANDARD=600};

if(errorCode >= SERVER_ERROR && errorCode < NON_STANDARD)

чем

if(errorCode >= 500 && errorCode < 600)

Ключевой частью является то, что они похожи на массивы! Но используются для приведения целочисленных значений.

Краткий пример:

enum Suit {Diamonds, Hearts, Clubs, Spades};
//does something with values in the enum past "Hearts" in this case
for(int i=0;i<4;i++){
   //Could also use i or Hearts, because the enum will turns these both back into an int 
   if( (Suit)(i) > 1 )
   {
      //Whatever we'd like to do with (Suit)(i)
   }
}

Часто перечисления также используются с массивами char* или строковыми массивами, чтобы вы могли напечатать какое-либо сообщение со связанным значением. Обычно это просто массивы с одинаковым набором значений в перечислении, например:

char* Suits[4] = {"Diamonds", "Hearts", "Clubs", "Spades"};
//Getting a little redundant
cout << Suits[Clubs] << endl;
//We might want to add this to the above
//cout << Suits[(Suit)(i)] << endl;

И, конечно, еще лучше создать универсальный класс, который обрабатывает итерации для перечислений, как и ответы выше.

Вы можете выполнять некоторые из предложенных методов выполнения статически с TMP.

#include <iostream>

enum abc
{
    a,
    b,
    c,
    end
};

void function_call(abc val)
{
    std::cout << val << std::endl;
}

template<abc val>
struct iterator_t
{
    static void run()
    {
        function_call(val);

        iterator_t<static_cast<abc>(val + 1)>::run();
    }
};

template<>
struct iterator_t<end>
{
    static void run()
    {
    }
};

int main()
{
    iterator_t<a>::run();

    return 0;
}

Выход из этой программы:

0
1
2

См. Гл. 1 Абрахамса, Гуртовой "Шаблон метапрограммирования на С ++" для хорошего рассмотрения этой техники. Преимущество такого подхода по сравнению с предлагаемыми методами выполнения состоит в том, что при оптимизации этого кода он может встроить статику и примерно эквивалентен следующему:

function_call(a);
function_call(b);
function_call(c);

Встроенный function_call для еще большей помощи от компилятора.

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

Другие вопросы по тегам