Как я могу перебрать перечисление?


304

Я только что заметил, что вы не можете использовать стандартные математические операторы для перечисления, такие как ++ или + =

Итак, каков наилучший способ перебрать все значения в перечислении C ++?


2
Один из многих подходов: когда enum просто не достаточно: классы перечисления для C ++ . И, если вы хотите что-то более инкапсулированное, попробуйте этот подход от Джеймса Канзе.
— Дон Уэйкфилд

Связанные элементы имеют несколько интересных ответов.
— Тони

Эти ответы не охватывают проблему, которая intможет быть недостаточно большой! ( [C++03: 7.2/5])
— Гонки легкости на орбите

Интересно, что вы можете определить operator++по перечислениям; тем не менее, так что вы можете сделать for(Enum_E e = (Enum_E)0; e < ENUM_COUNT; e++). Обратите внимание, что вы должны привести 0к, Enum_Eпотому что C ++ запрещает операторы присваивания для перечислений
— weberc2

Если бы существовал оператор времени компиляции, аналогичный тому, как работает sizeof, который мог бы генерировать литерал std :: initializer_list, состоящий из значений перечисления, у нас было бы решение и не было бы никаких накладных расходов времени выполнения.
— Джбруни

Ответы:


263

Типичный способ заключается в следующем:

enum Foo {
  One,
  Two,
  Three,
  Last
};

for ( int fooInt = One; fooInt != Last; fooInt++ )
{
   Foo foo = static_cast<Foo>(fooInt);
   // ...
}

Обратите внимание, перечисление Last предназначено для пропуска итерации. Используя это «поддельное» Lastперечисление, вам не нужно обновлять условие завершения в цикле for до последнего «реального» перечисления каждый раз, когда вы хотите добавить новое перечисление. Если вы хотите добавить другие перечисления позже, просто добавьте их до Last. Цикл в этом примере все еще будет работать.

Конечно, это ломается, если указаны значения перечисления:

enum Foo {
  One = 1,
  Two = 9,
  Three = 4,
  Last
};

Это показывает, что перечисление на самом деле не предназначено для итерации. Типичный способ работы с enum - это использовать его в операторе switch.

switch ( foo )
{
    case One:
        // ..
        break;
    case Two:  // intentional fall-through
    case Three:
        // ..
        break;
    case Four:
        // ..
        break;
     default:
        assert( ! "Invalid Foo enum value" );
        break;
}

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


13
Обратите внимание, что в первой части примера, если вы хотите использовать 'i' в качестве перечисления Foo, а не int, вам нужно будет выполнить статическое приведение типа: static_cast <Foo> (i)
— Clayton

5
Также вы пропускаете Последнее в цикле. Должно быть <= Последнее
— Тони

20
@Tony Last предназначен для пропуска. Если вы хотите добавить больше перечислений позже, добавьте их до Last ... цикл в первом примере все еще будет работать. Используя "поддельное" последнее перечисление, вам не нужно обновлять условие завершения в цикле for до последнего "реального" перечисления каждый раз, когда вы хотите добавить новое перечисление.
— Timidpueo

За исключением того, что вы фактически распределили память, когда перечисление при условии, что оно имеет нулевое индексирование и является строго непрерывным, может выполнить эту задачу без выделения памяти.
— Облако

1
Обратите внимание, что для того, чтобы это определение перечисления было безопасным для обновлений, необходимо определить значение UNKNOWN = 0. Кроме того, я бы предложил исключить defaultрегистр при переключении значений перечисления, поскольку это может скрыть случаи, когда обработка значений была забыта до времени выполнения. Вместо этого следует жестко закодировать все значения и использовать UNKNOWNполе для обнаружения несовместимостей.
— Бенджамин Банье

53
#include <iostream>
#include <algorithm>

namespace MyEnum
{
  enum Type
  {
    a = 100,
    b = 220,
    c = -1
  };

  static const Type All[] = { a, b, c };
}

void fun( const MyEnum::Type e )
{
  std::cout << e << std::endl;
}

int main()
{
  // all
  for ( const auto e : MyEnum::All )
    fun( e );

  // some
  for ( const auto e : { MyEnum::a, MyEnum::b } )
    fun( e );

  // all
  std::for_each( std::begin( MyEnum::All ), std::end( MyEnum::All ), fun );

  return 0;
}

Спасибо! Обратите внимание, что если вы пересекаете файлы / классы и если совместимость с MS создает проблемы с объявленными в заголовке нецелыми константами, в моем компиляторе помогает явно указать размер в типе в заголовке: static const Type All[3];и тогда я могу для инициализации в источнике: const MyEnum::Type MyEnum::All[3] = { a, b, c }; перед этим я получал неприятные Error in range-based for...ошибки (потому что массив имел неизвестный размер). Понял это благодаря связанному ответу
— мудрец

1
Версия массива очень дружелюбна к копированию и вставке. Наиболее удовлетворительный ответ, кроме того, «НЕТ» или «только для последовательного». Вероятно, макрос дружественный даже.
— Пауло Невес

1
это может быть хорошим решением для перечислений с небольшим количеством элементов, но для перечислений с большим количеством элементов оно не должно подходить.
— kato2

20

Если ваше перечисление начинается с 0, а приращение всегда равно 1.

enum enumType 
{ 
    A = 0,
    B,
    C,
    enumTypeEnd
};

for(int i=0; i<enumTypeEnd; i++)
{
   enumType eCurrent = (enumType) i;            
}

Если нет, я думаю, единственное, почему нужно создать что-то вроде

vector<enumType> vEnums;

добавить элементы и использовать обычные итераторы ....


19

С c ++ 11, на самом деле, есть альтернатива: написание простого шаблонного пользовательского итератора.

давайте предположим, что ваше перечисление

enum class foo {
  one,
  two,
  three
};

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

#include <type_traits>
template < typename C, C beginVal, C endVal>
class Iterator {
  typedef typename std::underlying_type<C>::type val_t;
  int val;
public:
  Iterator(const C & f) : val(static_cast<val_t>(f)) {}
  Iterator() : val(static_cast<val_t>(beginVal)) {}
  Iterator operator++() {
    ++val;
    return *this;
  }
  C operator*() { return static_cast<C>(val); }
  Iterator begin() { return *this; } //default ctor is good
  Iterator end() {
      static const Iterator endIter=++Iterator(endVal); // cache it
      return endIter;
  }
  bool operator!=(const Iterator& i) { return val != i.val; }
};

Вам нужно будет специализировать это

typedef Iterator<foo, foo::one, foo::three> fooIterator;

И тогда вы можете итерировать с помощью диапазона для

for (foo i : fooIterator() ) { //notice the parentheses!
   do_stuff(i);
}

Предположение, что у вас нет пробелов в вашем перечислении, все еще верно; нет никакого предположения о количестве битов, фактически необходимых для хранения значения перечисления (благодаря std :: basic_type)


1
@lepe? Вы просто создаете другой typedef для другого перечисления.
— Андрей Лазарь

2
@lepe Это все равно что говорить, что std::vectorэто не универсально, потому что std::vector<foo>связано с foo.
— Кайл Стрэнд

1
typedef Iterator<color, color::green, color::red> colorIterator;Убедитесь, что вы понимаете, как работают экземпляры шаблона.
— Андрей Лазарь

2
О, я вижу проблему - foo operator*() { ...должно быть C operator*() { ....
— Кайл Стрэнд

1
@KyleStrand: Вы получили это! это имеет смысл сейчас. Код должен быть обновлен? Спасибо всем за ваши объяснения.
— Лепе

16

Слишком сложное решение, я делаю так:

enum NodePosition { Primary = 0, Secondary = 1, Tertiary = 2, Quaternary = 3};

const NodePosition NodePositionVector[] = { Primary, Secondary, Tertiary, Quaternary };

for (NodePosition pos : NodePositionVector) {
...
}

Я не знаю, почему это было понижено. Это разумное решение.
— Пол Браннан

11
Я ожидаю, что это потому, что записи должны поддерживаться в двух местах.
— Муравей

Разрешает ли C ++ for (NodePosition pos : NodePositionVector)синтаксис? Насколько я знаю, это синтаксис Java, и вам понадобятся итераторы в C ++, чтобы сделать что-то эквивалентное.
— thegreatjedi

3
@thegreatjedi Начиная с C ++ 11 вы можете, даже проще: for (auto pos: NodePositionVector) {..}
— Enzojz

@thegreatjedi Было бы быстрее найти или даже скомпилировать тестовую программу, чем задать этот вопрос. Но да, начиная с C ++ 11, это совершенно правильный синтаксис C ++, который компилятор переводит в эквивалентный (и гораздо более подробный / менее абстрактный) код, обычно через итераторы; см. cppreference . И, как сказал Enzojz, C ++ 11 также добавил auto, так что вам не нужно явно объявлять тип элементов, если вам (A) не нужно использовать оператор преобразования или (B) autoпо какой-то причине не нравится , Большинство forпользователей диапазона используют autoAFAICT
— underscore_d

9

Я часто так делаю

    enum EMyEnum
    {
        E_First,
        E_Orange = E_First,
        E_Green,
        E_White,
        E_Blue,
        E_Last
    }

    for (EMyEnum i = E_First; i < E_Last; i = EMyEnum(i + 1))
    {}

или, если не последовательно, но с регулярным шагом (например, битовые флаги)

    enum EAnimalCaps
    {
        E_First,
        E_None    = E_First,
        E_CanFly  = 0x1,
        E_CanWalk = 0x2
        E_CanSwim = 0x4,
        E_Last
    }

    class MyAnimal
    {
       EAnimalCaps m_Caps;
    }

    class Frog
    {
        Frog() : 
            m_Caps(EAnimalCaps(E_CanWalk | E_CanSwim))
        {}
    }

    for (EAnimalCaps= E_First; i < E_Last; i = EAnimalCaps(i << 1))
    {}

но какая польза от распечатки значений побитовым образом?
— Ану

1
Использовать перечисления для создания битовых масок. например, объединить несколько параметров в одну переменную, а затем использовать FOR для проверки каждого параметра. Исправлен мой пост с лучшим примером.
— Ники,

Я до сих пор не могу его использовать (и ваш пост все еще показывает старый пример)! Использование enum в качестве битовых масок действительно полезно, но не удалось соединить точки! Не могли бы вы подробнее рассказать о вашем примере, вы также можете добавить дополнительный код.
— Ану

@anu Извините, не увидел ваш комментарий. В качестве примера битовой маски добавлен класс Frog
— Ники,

8

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

Общепринятым соглашением является присвоение имени последнему перечисляемому значению что-то вроде MAX и использование его для управления циклом с использованием int.


Здесь есть несколько примеров, демонстрирующих обратное. По твоему утверждению ты сам себе противоречишь (вторая строка).
— Ники,

6

Что-то, что не было рассмотрено в других ответах = если вы используете строго типизированные перечисления C ++ 11, вы не можете использовать ++или + intдля них. В этом случае требуется более сложное решение:

enum class myenumtype {
  MYENUM_FIRST,
  MYENUM_OTHER,
  MYENUM_LAST
}

for(myenumtype myenum = myenumtype::MYENUM_FIRST;
    myenum != myenumtype::MYENUM_LAST;
    myenum = static_cast<myenumtype>(static_cast<int>(myenum) + 1)) {

  do_whatever(myenum)

}

3
... но C ++ 11 вводит диапазон, основанный на том, что показано в других ответах. :-)
— мудрец

5

Вы можете попытаться определить следующий макрос:

#define for_range(_type, _param, _A1, _B1) for (bool _ok = true; _ok;)\
for (_type _start = _A1, _finish = _B1; _ok;)\
    for (int _step = 2*(((int)_finish)>(int)_start)-1;_ok;)\
         for (_type _param = _start; _ok ; \
 (_param != _finish ? \
           _param = static_cast<_type>(((int)_param)+_step) : _ok = false))

Теперь вы можете использовать его:

enum Count { zero, one, two, three }; 

    for_range (Count, c, zero, three)
    {
        cout << "forward: " << c << endl;
    }

Его можно использовать для итерации вперед и назад через unsigned, integer, enums и chars:

for_range (unsigned, i, 10,0)
{
    cout << "backwards i: " << i << endl;
}


for_range (char, c, 'z','a')
{
    cout << c << endl;
}

Несмотря на неловкое определение, он очень хорошо оптимизирован. Я посмотрел на дизассемблер в VC ++. Код чрезвычайно эффективен. Не стоит откладывать, кроме трех утверждений for: компилятор выдаст только один цикл после оптимизации! Вы даже можете определить вложенные циклы:

unsigned p[4][5];

for_range (Count, i, zero,three)
    for_range(unsigned int, j, 4, 0)
    {   
        p[i][j] = static_cast<unsigned>(i)+j;
    }

Очевидно, вы не можете перебирать перечисляемые типы с пробелами.


1
Это замечательный хак! Хотя это более подходит для C, чем для C ++, можно сказать.
— einpoklum

3
_A1это не разрешенное имя, это подчеркивание со следующей заглавной буквой.
— Мартин Уединг

3

Вы также можете перегрузить операторы увеличения / уменьшения для вашего перечисляемого типа.


1
Вы не можете перегружать любые операторы в перечисляемых типах C или C ++. Если только вы не должны были создать структуру / класс, который эмулировал бы перечисление значений.
— Тревор Хикки

2
C ++ позволяет перегружать операторы в перечислениях. См. Stackoverflow.com/questions/2571456/… .
— Арка Д. Робисон

Чрезмерное увеличение / уменьшение требует принятие решения о том , что делать , когда происходит переполнение
— Одноименное

3

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

enum Item { Man, Wolf, Goat, Cabbage }; // or enum class

for (auto item : {Wolf, Goat, Cabbage}) { // or Item::Wolf, ...
    // ...
}

Это хороший вариант, я думаю. Должно ли быть частью более новой спецификации C ++, чем я использовал, когда задавал вопрос, который угадаю?
— Адам

Да. Он перебирает std :: initializer_list <Item>. ссылка .
— Марски

2

Если вам не нравится загрязнять ваше перечисление конечным элементом COUNT (потому что, возможно, если вы также используете перечисление в переключателе, тогда компилятор предупредит вас о пропущенном регистре COUNT :), вы можете сделать это:

enum Colour {Red, Green, Blue};
const Colour LastColour = Blue;

Colour co(0);
while (true) {
  // do stuff with co
  // ...
  if (co == LastColour) break;
  co = Colour(co+1);
}

2

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

enum Bar {
    One = 1,
    Two,
    Three,
    End_Bar // Marker for end of enum; 
};

for (Bar foo = One; foo < End_Bar; foo = Bar(foo + 1))
{
    // ...
}

1
Увеличение может быть сокращено до foo = Bar(foo + 1).
— HolyBlackCat

Спасибо, HolyBlackCat, я включил ваше отличное предложение! Я также заметил, что Riot имеет почти то же самое решение, но соответствующее строгой типизации (и, следовательно, более многословное).
— Итан Брэдфорд

2
enum class A {
    a0=0, a3=3, a4=4
};
constexpr std::array<A, 3> ALL_A {A::a0, A::a3, A::a4}; // constexpr is important here

for(A a: ALL_A) {
  if(a==A::a0 || a==A::a4) std::cout << static_cast<int>(a);
}

A constexpr std::arrayможет повторять даже непоследовательные перечисления без создания экземпляра массива компилятором. Это зависит от таких вещей, как эвристика оптимизации компилятора и от того, берете ли вы адрес массива.

В моих экспериментах я обнаружил, что g++9.1 с -O3оптимизирует вышеприведенный массив, если есть 2 непоследовательных значения или довольно много последовательных значений (я проверял до 6). Но он делает это только если у вас есть ifзаявление. (Я пробовал оператор, который сравнивал целочисленное значение больше, чем все элементы в последовательном массиве, и он включал итерацию, хотя ни один из них не был исключен, но когда я пропустил оператор if, значения были помещены в память.) значения из непоследовательного перечисления в [одном случае | https://godbolt.org/z/XuGtoc] . Я подозреваю, что это странное поведение связано с глубокой эвристикой, связанной с кэшем и предсказанием ветвлений.

Вот ссылка на простую итерацию теста на Godbolt, которая демонстрирует, что массив не всегда создается.

Ценой этой техники является написание элементов enum дважды и синхронизация двух списков.


Мне нравится простая семантика циклического цикла, и я думаю, что она будет развиваться еще больше, поэтому мне нравится это решение.
— rtischer8277

2

В книге Бьярна Страуструпа по языку программирования C ++ вы можете прочитать, что он предлагает перегрузить ее operator++для ваших конкретных задач enum.enumявляются определяемыми пользователем типами и оператор перегрузки существует на языке для этих конкретных ситуаций.

Вы сможете кодировать следующее:

#include <iostream>
enum class Colors{red, green, blue};
Colors& operator++(Colors &c, int)
{
     switch(c)
     {
           case Colors::red:
               return c=Colors::green;
           case Colors::green:
               return c=Colors::blue;
           case Colors::blue:
               return c=Colors::red; // managing overflow
           default:
               throw std::exception(); // or do anything else to manage the error...
     }
}

int main()
{
    Colors c = Colors::red;
    // casting in int just for convenience of output. 
    std::cout << (int)c++ << std::endl;
    std::cout << (int)c++ << std::endl;
    std::cout << (int)c++ << std::endl;
    std::cout << (int)c++ << std::endl;
    std::cout << (int)c++ << std::endl;
    return 0;
}

тестовый код: http://cpp.sh/357gb

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


На этот пост был подан отрицательный ответ. Любая причина, почему бы не ответить на вопрос?
— LAL

Причина, вероятно, в том, что это ужасное архитектурно говоря решение: оно заставляет вас писать глобальную логику, предназначенную для конкретного компонента (вашего перечисления), более того, если ваше перечисление действительно меняется по какой-то причине, вы вынуждены редактировать свой + + оператор, так как этот подход не является устойчивым для любого проекта среднего масштаба, неудивительно, что он исходит из рекомендации Бьярно Страуструпа, в те времена, когда архитектура программного обеспечения была похожа на научную фантастику
— Манджа

Оригинальный вопрос о том, чтобы иметь оператора enum. Это был не архитектурный вопрос. Я не верю, что в 2013 году C ++ был научной фантастикой.
— LAL

1

Для MS-компиляторов:

#define inc_enum(i) ((decltype(i)) ((int)i + 1))

enum enumtype { one, two, three, count};
for(enumtype i = one; i < count; i = inc_enum(i))
{ 
    dostuff(i); 
}

Примечание: это намного меньше кода, чем простой шаблонный пользовательский ответ итератора.

Вы можете заставить это работать с GCC, используя typeofвместо этого decltype, но у меня нет этого компилятора под рукой, чтобы убедиться, что он компилируется.


Это было написано примерно через 5 лет после того, как decltypeстало стандартом C ++, поэтому не стоит рекомендовать устаревшие typeofиз древних GCC. Смутно недавний GCC справляется decltypeпросто отлично. Есть и другие проблемы: отбрасывание в стиле C не рекомендуется, а макросы хуже. Правильные функции C ++ могут дать ту же общую функциональность. Это было бы лучше переписать использовать static_castи шаблонную функцию: template <typename T> auto inc_enum(T const t) { return static_cast<T>(static cast<int>(t) + 1); }. И броски не нужны для не enum class. Кроме того, операторы могут быть перегружены по enumтипу (TIL)
— underscore_d

1
typedef enum{
    first = 2,
    second = 6,
    third = 17
}MyEnum;

static const int enumItems[] = {
    first,
    second,
    third
}

static const int EnumLength = sizeof(enumItems) / sizeof(int);

for(int i = 0; i < EnumLength; i++){
    //Do something with enumItems[i]
}

Это решение создаст ненужные статические переменные в памяти, в то время как цель enum состоит в том, чтобы просто создать «маску» для встроенных констант
— TheArquitect

Если не изменено наconstexpr static const int enumItems[]
— Мохаммад Канан

0

В C ++ нет самоанализа, поэтому вы не можете определить такие вещи во время выполнения.


1
Не могли бы вы объяснить мне, почему «самоанализ» был бы необходим для итерации по перечислению?
— Джонатан Ми

Может быть, термин « отражение» ?
— kͩeͣmͮpͥ ͩ

2
Я пытаюсь сказать 2 вещи: 1) Во многих других ответах C ++ может выполнить это, поэтому, если вы собираетесь сказать, что это невозможно, требуется ссылка или дальнейшие разъяснения. 2) В нынешнем виде это в лучшем случае комментарий, а не ответ.
— Джонатан Ми


1
Я снова добавлю 2 комментария: 1) Я не понижаю голос, потому что я считаю, что получение понижающего голоса демотивирует участие на сайте, я считаю это контрпродуктивным 2) Я все еще не понимаю, что вы пытаетесь сказать, но это звучит как Вы понимаете что-то, чего я не знаю, и в этом случае я бы предпочел, чтобы вы уточнили, а не удалили отрицательный ответ.
— Джонатан Ми

0

Если вы знали, что значения перечисления были последовательными, например, перечисление Qt: Key, вы могли бы:

Qt::Key shortcut_key = Qt::Key_0;
for (int idx = 0; etc...) {
    ....
    if (shortcut_key <= Qt::Key_9) {
        fileMenu->addAction("abc", this, SLOT(onNewTab()),
                            QKeySequence(Qt::CTRL + shortcut_key));
        shortcut_key = (Qt::Key) (shortcut_key + 1);
    }
}

Работает как положено.


-1

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

Если enum это:

enum MyEnumType{Hay=12,Grass=42,Beer=39};

затем создайте массив:

int Array[] = {Hay,Grass,Beer,-1};

for (int h = 0; Array[h] != -1; h++){
  doStuff( (MyEnumType) Array[h] );
}

Это не ломается независимо от целых чисел в представлении, пока проверка -1 не сталкивается ни с одним из элементов, конечно.

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.