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

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

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

У связанных элементов есть несколько интересных ответов.

Tony 13.06.2013 05:27

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

Lightness Races in Orbit 21.06.2013 14:03

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

weberc2 26.06.2014 02:52

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

jbruni 14.12.2017 02:56

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

Don Wakefield 04.11.2008 19:22
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
336
5
353 256
23
Перейти к ответу Данный вопрос помечен как решенный

Ответы 23

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

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

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

Niki 31.12.2019 15:54
Ответ принят как подходящий

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

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

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

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

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

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

Clayton 18.03.2009 01:30

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

Tony 13.06.2013 05:24

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

timidpueo 04.08.2013 01:06

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

Cloud 18.01.2014 03:02

Было бы здорово вставить комментарий @Tony в ответ. Меня это действительно сбило с толку.

tsusanka 25.10.2014 01:30

Вы также можете использовать макросы, но это означает, что вам также необходимо определить свое перечисление через макрос. Таким образом, вы действительно избегаете кастинга и вообще не должны использовать вектор!

ChaoSXDemon 23.01.2016 21:56

@tsusanka На самом деле он работает по той же логике, что и итерация по контейнеру STL. Так же, как std::end() возвращает итератор «после окончания», поэтому вы можете использовать i < std::end(whatever) или it != std::end(whatever), может быть полезно добавить фиктивное значение в конце enum, чтобы вы могли использовать i < Last. Точно так же, если перечисление предназначено для использования для итерации, также может быть полезно добавить фиктивный элемент First, базовое значение которого совпадает с фактическим значением первого элемента. В этом ответе, например, это может быть что-то вроде First = One.

Justin Time - Reinstate Monica 01.04.2016 00:18

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

Benjamin Bannier 13.04.2017 18:08

@timidpueo Вот почему я предпочитаю называть последнюю запись Count. Делает это немного более очевидным.

Cerno 14.08.2020 11:27

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

Не могли бы вы объяснить мне, почему для перебора перечисления потребуется «самоанализ»?

Jonathan Mee 07.09.2016 21:50

Может термин Отражение?

kͩeͣmͮpͥ ͩ 08.09.2016 14:15

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

Jonathan Mee 08.09.2016 14:33

Тогда проголосуйте против моего ответа - я думаю, вы его более чем оправдали

kͩeͣmͮpͥ ͩ 08.09.2016 18:49

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

Jonathan Mee 08.09.2016 19:04

C++ действительно поддерживает самоанализ типов. См. Typeid и dynamic_cast

user3063349 27.04.2017 11:36

Самоанализ - правильный термин (никаких манипуляций, только осмотр). Хотя утверждение ответов неверно из-за методов RTTI через typeid и dynamic_cast, оба они не являются решением проблемы. Так что технически «самоанализ C++ не охватывает поиск значений перечисления» или что-то еще было бы лучшим ответом.

Roi Danton 03.06.2018 20:54

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

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

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

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

vector<enumType> vEnums;

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

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

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

Trevor Hickey 02.09.2015 02:34

C++ допускает перегрузку операторов перечислений. См. stackoverflow.com/questions/2571456/….

Arch D. Robison 10.05.2018 00:53

Перегрузка приращения / декремента требует принятия решения о том, что делать при переполнении.

Eponymous 27.05.2019 21:39

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

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

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

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

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

Замечательный хак! Хотя, можно сказать, он больше подходит для C, чем для C++.

einpoklum 27.06.2013 15:29
_A1 - недопустимое имя, это ведущее подчеркивание с последующей заглавной буквой.
Martin Ueding 10.02.2017 22:03

То, что не было рассмотрено в других ответах = если вы используете строго типизированные перечисления 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)

}

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

sage 04.03.2015 08:52

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

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

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

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

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

Paul Brannan 19.12.2014 16:27

Я полагаю, это произошло потому, что записи нужно хранить в двух местах.

Ant 29.05.2015 16:51

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

thegreatjedi 17.12.2015 05:17

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

Enzojz 13.01.2016 15:44

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

underscore_d 15.09.2018 15:42

Вот расширение этого подхода: enum class Foo { a, b }; template <typename T> struct enum_values; template <> struct enum_values<Foo> { static constexpr std::array<Foo,2> values() { return { Foo::a, Foo::b }; } }; Затем вы можете использовать for (Foo f : enum_values<Foo>::values()), поэтому вам не нужно иметь имена переменных, названные по типам (NodePositionVector). Конечно, вам все еще нужно поддерживать его. Было бы неплохо, если бы язык предоставлял такую ​​функциональность.

Ben 16.11.2018 19:09

@Enzojz: Просто хочу подчеркнуть, что этот код не работает с C++ старше 11: range-based 'for' loops only available with '-std=c++11'

kuga 05.01.2021 11:44
#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... (потому что массив имел неизвестный размер). Разобрался благодаря связанный ответ

sage 04.03.2015 08:47

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

Paulo Neves 29.09.2016 18:14

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

kato2 04.09.2019 18:48

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

И затем вы можете выполнить итерацию, используя range-for

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

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

обратите внимание, что «общий код» привязан к «foo» (см. код посередине), поэтому вы не можете использовать его с другими определениями перечислений.

lepe 14.09.2015 10:11

@lepe? Вы просто делаете другой typedef для другого перечисления.

Andrew Lazarus 25.09.2015 00:49

@lepe Это все равно что сказать, что std::vector не является универсальным, потому что std::vector<foo> привязан к foo.

Kyle Strand 03.12.2015 00:15

@AndrewLazarus, @Kyle: Итак, если у меня есть другое перечисление, например: enum color { green = 20, white = 30, red = 40 }, как я могу использовать приведенный выше код, не меняя часть foo operator*() { ...? Может кто-нибудь объяснить с помощью примера кода? (Извините, мои навыки C++ еще не настолько развиты).

lepe 03.12.2015 03:43

@lepe Из-за пропуска вам придется специализировать оператор ++, чтобы использовать val + = 10 вместо ++ val.

Andrew Lazarus 03.12.2015 05:57

@AndrewLazarus: извините, моя проблема ... Я установил эти значения, чтобы они отличались от foo, но меня это не беспокоит. Пожалуйста, измените мой предыдущий комментарий и установите enum равным: enum color { green, black, white, blue, red }. Как мне использовать код, предложенный Франческо, чтобы я мог повторять либо foo, либо color, не изменяя его код? Если возможно, я хотел бы увидеть пример кода.

lepe 03.12.2015 08:16
typedef Iterator<color, color::green, color::red> colorIterator; Убедитесь, что вы понимаете, как работают экземпляры шаблонов.
Andrew Lazarus 03.12.2015 20:12

@lepe У каждого перечисления будет свой тип итератора своя (специализированный на шаблоне), точно так же, как вам понадобится другой тип std::vector для хранения различных типов объектов. Это достигается за счет специализации шаблона, как в предыдущем комментарии. Дело в том, что вам не нужно переопределять шаблон Iterator для каждого уникального типа перечисления, точно так же, как вам не нужно переопределять шаблон std::vector для каждого типа объекта; вы просто специализируете это, что можно сделать с однострочным typedef.

Kyle Strand 03.12.2015 21:07

@lepe Чтобы решить проблему «пропусков» более общим (хотя и ограниченным) способом, см. мой ответ ниже. Также обратите внимание, что вы можете отметить в комментарии только одного человека (поэтому я не получал уведомления, когда вы пытались отметить меня).

Kyle Strand 03.12.2015 21:09

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

Kyle Strand 04.12.2015 02:08

@KyleStrand: Ты понял! теперь это имеет смысл. Следует ли обновлять код? Спасибо всем за ваши объяснения.

lepe 04.12.2015 03:44

На самом деле я создал здесь несколько более общую версию: github.com/BatmanAoD/PublicPaste/blob/master/GenericCppCode/‌…

Kyle Strand 04.12.2015 20:31

И под «более общим» я подразумеваю, что он полагается на перегруженную глобальную функцию IsEnumValid (которую можно легко переместить в пространство имен), чтобы определить, являются ли определенные int-значения допустимыми значениями перечисления, что решает проблему «пропуска». Один из альтернативных вариантов - сделать функцию проверки достоверности одним из параметров шаблона. Другой вариант - потребовать наличия статического массива для итерации, как в stackoverflow.com/a/26910769/1858225

Kyle Strand 04.12.2015 20:42

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

mwpowellhtx 04.12.2018 02:47

Для компиляторов 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 15.09.2018 15:55

Если вам не нравится загрязнять ваше перечисление последним элементом 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);
}

Если бы вы знали, что значения перечисления были последовательными, например перечисление 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);
    }
}

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

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

    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))
    {}

но что толку от побитовой печати значений?

Anu 08.01.2019 04:31

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

Niki 08.01.2019 11:37

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

Anu 08.01.2019 22:46

@anu К сожалению, не видел вашего комментария. Добавлен класс Frog как пример битовой маски

Niki 31.12.2019 15:50

Разве вам не нужно начинать с элемента 0x1? В противном случае вы будете сдвигать много нулей и, таким образом, оставаться в первом элементе бесконечно

Tare 16.02.2021 11:09

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

Если перечисление:

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

Вот еще одно решение, которое работает только для непрерывных перечислений. Он дает ожидаемую итерацию, за исключением уродства в приращении, которому он и принадлежит, поскольку это то, что сломано в 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))
{
    // ...
}

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

HolyBlackCat 08.11.2018 22:07

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

Ethan Bradford 08.11.2018 22:49
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);
}

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

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

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

Цена этого метода состоит в том, чтобы дважды написать элементы перечисления и синхронизировать два списка.

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

rtischer8277 24.03.2020 17:56

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

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

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

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

Adam 16.07.2019 00:08

да. Он выполняет итерацию по std :: initializer_list <Item>. связь.

marski 23.07.2019 01:25
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 - просто создать `` маску '' для встроенных констант.

TheArchitect 11.12.2019 22:09

Если не поменял на constexpr static const int enumItems[]

Mohammad Kanan 02.03.2020 03:05

В книге Бьярна Страуструпа по языку программирования 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 14.12.2019 00:07

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

Manjia 05.03.2020 14:37

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

LAL 06.03.2020 17:03

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

Manjia 03.03.2021 14:11

Расширение ответа @ Eponymous: это здорово, но не дает общего синтаксиса. Вот что я придумал:

// Common/EnumTools.h
#pragma once

#include <array>

namespace Common {

// Here we forward-declare metafunction for mapping enums to their values.
// Since C++<23 doesn't have reflection, you have to populate it yourself :-(
// Usage: After declaring enum class E, add this overload in the namespace of E:
// inline constexpr auto allValuesArray(const E&, Commob::EnumAllValuesTag) { return std::array{E::foo, E::bar}; }
// Then `AllValues<NS::E>` will call `allValuesArray(NS::E{}, EnumAllValuesTag)` which will resolve
// by ADL.
// Just be sure to keep it sync'd with your enum!

// Here's what you want to use in, e.g., loops: "for (auto val : Common::AllValues<MyEnum>) {"

struct EnumAllValuesTag {}; // So your allValuesArray function is clearly associated with this header.

template <typename Enum>
static inline constexpr auto AllValues = allValuesArray(Enum{}, EnumAllValuesTag{});
// ^ Just "constexpr auto" or "constexpr std::array<Enum, allValuesArray(Enum{}, EnumAllValuesTag{}).size()>" didn't work on all compilers I'm using, but this did.

} // namespace Common

затем в вашем пространстве имен:

#include "Common/EnumTools.h"

namespace MyNamespace {

enum class MyEnum {
    foo,
    bar = 4,
    baz = 42,
};

// Making this not have to be in the `Common` namespace took some thinking,
// but is a critical feature since otherwise there's no hope in keeping it sync'd with the enum.
inline constexpr auto allValuesArray(const MyEnum&, Common::EnumAllValuesTag) {
    return std::array{ MyEnum::foo, MyEnum::bar, MyEnum::baz };
}

} // namespace MyNamespace

затем везде, где вам нужно его использовать:

for (const auto& e : Common::AllValues<MyNamespace::MyEnum>) { ... }

так что даже если вы напечатали:

namespace YourNS {
using E = MyNamespace::MyEnum;
} // namespace YourNS

for (const auto& e : Common::AllValues<YourNS::E>) { ... }

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

Будущая работа:

  1. Вы должны иметь возможность добавить функцию constexpr (и, следовательно, метафункцию), которая фильтрует Common::AllValues<E>, чтобы предоставить Common::AllDistinctValues<E> для случая перечислений с повторяющимися числовыми значениями, например enum { foo = 0, bar = 0 };.
  2. Бьюсь об заклад, есть способ использовать switch-Covers-all-enum-values ​​компилятора для записи allValuesArray, что приведет к ошибке, если перечисление добавило значение.

Это интересно. В итоге я использовал только inline constexpr auto allValuesArray() { return std::array{ MyEnum::foo, MyEnum::bar, MyEnum::baz }; }, так как мне все равно нужно объяснять его по одному. Для меня это самое простое решение. (Между прочим: с вашим кодом я увидел, как clang-7 вылетает. Fun Fun Fun XD)

kwach 08.12.2020 05:23

Плюсы: перечисления могут иметь любые значения, которые вам нравятся, в любом порядке, и их по-прежнему легко перебирать. Имена и значения определяются один раз в первом #define.

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

//create a, b, c, d as 0, 5, 6, 7
#define LIST x(a) x(b,=5) x(c) x(d)
#define x(n, ...) n __VA_ARGS__,
enum MyEnum {LIST}; //define the enum
#undef x //needed
#define x(n,...) n ,
MyEnum myWalkableEnum[] {LIST}; //define an iterable list of enum values
#undef x //neatness

int main()
{
  std::cout << d;
  for (auto z : myWalkableEnum)
    std::cout << z;
}
//outputs 70567

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

Большинство решений основаны на циклах в диапазоне (MIN, MAX), но не учитывают тот факт, что в перечислении могут быть дыры.

Мои предложения:

        for (int i = MYTYPE_MIN; i <= MYTYPE_MAX; i++) {
            if (MYTYPE_IsValid(i)) {
                MYTYPE value = (MYTYPE)i;
                // DoStuff(value)
            }   
        }   
        

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