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

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

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

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

Вероятные дубликаты:


person jameszhao00    schedule 07.09.2009    source источник
comment
Как вы предполагаете выбрать функцию для звонка? Не могли бы вы опубликовать какой-нибудь псевдокод, как вы собираетесь это делать? Это может помочь нам помочь вам.   -  person Kirill V. Lyadvinsky    schedule 08.09.2009
comment
Для времени выполнения посмотрите stackoverflow.com/questions/1292426/. (Если бы не время компиляции, ваш вопрос был бы точной его копией.)   -  person sbi    schedule 08.09.2009


Ответы (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);
}
person Johannes Schaub - litb    schedule 08.09.2009

В настоящее время 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));
    }
}
person StackedCrooked    schedule 07.09.2009

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

person Konrad Rudolph    schedule 07.09.2009

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

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. Код отправки трогать не нужно.

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

person Steve Jessop    schedule 07.09.2009

No

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

person DigitalRoss    schedule 07.09.2009

Мне это кажется хакерским, но может соответствовать вашим целям:

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, чтобы изменить его позже на что-то лучшее :-).

person Tom    schedule 07.09.2009
comment
for и вложенный switch совершенно бесполезны / бессмысленны, поскольку вы все равно используете каждое из последовательных значений. Просто опустите оба и выполните foo stuff, а затем bar stuff и т. Д. Напрямую. - person Konrad Rudolph; 08.09.2009
comment
Это полезно только в тестовой программе, в которой вы запускаете серию тестов. Условия начала и окончания будут переменными, поэтому, если вы хотите запустить только один тест, установите start = end: for (int i = start; i ‹= end; ++ i) {....} Во всех остальных случаях Я согласен с Конрадом: переключатель внутри for, вероятно, плохой дизайн. - person jmucchiello; 08.09.2009
comment
Цикл for для каждого случая оператора switch? Пожалуйста. Это Stackoverflow, а не thedailywtf.com. Это просто излишне сложный способ сделать что-то foo, а затем запретить. Это не требует какого-либо управления потоком. - person Alan; 08.09.2009
comment
@ Конрад и Алан: Я думаю, что суть этого в том, чтобы убедиться, что вы попали в каждое перечисление. Если вы аварийно завершаете работу или выводите предупреждение или что-то в этом роде в операторе по умолчанию, вы, по крайней мере, узнаете во время выполнения, что забыли включить новый случай после добавления нового перечисления. Я действительно не был уверен, что это за приложение к OP, поэтому я просто записал то, что, по моему мнению, было самым простым способом сделать то, что меня просили. Кажется, исходный вопрос был обновлен, и это больше не имеет смысла, и я неправильно понял исходный вопрос. Однако ... это немного лучше, чем просто вызывать каждый метод по отдельности. - person Tom; 08.09.2009

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

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); }
person sp2danny    schedule 25.10.2014

Вы можете выполнять некоторые из предложенных методов выполнения статически с помощью 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 Abrahams, Gurtovoy «Метапрограммирование шаблонов C ++», где подробно рассматривается этот метод. Преимущество этого способа по сравнению с предлагаемыми методами выполнения заключается в том, что при оптимизации этого кода он может встроить статику и примерно эквивалентен:

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

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

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

person Ken Smith    schedule 02.08.2010

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

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

Если бы у нас было перечисление, определяющее коды ошибок 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;

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

person TheUnknownGeek    schedule 12.11.2014