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

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

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

Андреас Buykx
источник
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;
}
ZDF
источник
Спасибо! Обратите внимание, что если вы пересекаете файлы / классы и если совместимость с 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) {
...
}
Enzojz
источник
Я не знаю, почему это было понижено. Это разумное решение.
Пол Браннан
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))
    {}
Niki
источник
но какая польза от распечатки значений побитовым образом?
Ану
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

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

JohnMcG
источник
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, ...
    // ...
}
Marski
источник
Это хороший вариант, я думаю. Должно ли быть частью более новой спецификации 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
источник
На этот пост был подан отрицательный ответ. Любая причина, почему бы не ответить на вопрос?
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, но у меня нет этого компилятора под рукой, чтобы убедиться, что он компилируется.

user2407277
источник
Это было написано примерно через 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 ++ нет самоанализа, поэтому вы не можете определить такие вещи во время выполнения.

kͩeͣmͮpͥ ͩ
источник
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);
    }
}

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

kcrossen
источник
-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 не сталкивается ни с одним из элементов, конечно.

mathreadler
источник