Як я можу повторити перелік?


304

Я щойно помітив, що ти не можеш використовувати стандартних математичних операторів на enum, такий як ++ або + =

Отже, який найкращий спосіб перебрати всі значення в перерахунку C ++?


2
Один з багатьох підходів: Коли перерахунок просто не вистачає: перелічувальні класи для 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 :: inicijalizer_list, що складається зі значень enum, у нас було б рішення та не було б задіяно накладні витрати часу.
jbruni

Відповіді:


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

Це ілюструє, що перерахунок насправді не призначений для перебору через. Типовий спосіб поводження з перерахунком - це використовувати його в операторі переключення.

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

Якщо ви дійсно хочете перерахувати, введіть значення перерахунку у вектор і повторіть це. Це також буде належним чином відповідати заданим значенням enum.


13
Зауважте, що в першій частині прикладу, якщо ви хочете використовувати "i" як переліг Foo, а не int, вам потрібно буде статичний вигляд, як: static_cast <Foo> (i)
Clayton,

5
Також ви пропускаєте Останнє в циклі. Має бути <= Останнє
Тоні

20
@Tony Last призначений для пропуску. Якщо ви хочете додати більше перерахунків пізніше, додайте їх до Last ... цикл у першому прикладі все ще працюватиме. Використовуючи "підроблений" Останній перерахунок, вам не доведеться оновлювати свою завершальну умову в циклі for до останнього "справжнього" перерахунку щоразу, коли ви бажаєте додати нову кількість.
timidpueo

За винятком того, що ви фактично виділили пам'ять, коли enum, за умови нульового індексу та суворо неперервного, може виконати це завдання, не виділяючи пам'ять.
Хмара

1
Зауважте, що для визначення перерахунку, що є безпечним для оновлень, слід визначити значення UNKNOWN = 0. Крім того, я б запропонував просто відмовитись від defaultвипадку при переключенні значень enum, оскільки це може приховати випадки, коли обробка значень була забута до часу виконання. Натомість слід твердо кодувати всі значення та використовувати 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;

І тоді ви можете повторити, використовуючи range-for

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

Припущення, що у вас немає прогалин у вашій перерахунку, все ще вірно; немає припущення щодо кількості бітів, фактично необхідних для зберігання значення enum (завдяки std :: underlying_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: Ви зрозуміли! це має цілком сенс зараз. Чи повинен бути оновлений код? Дякуємо всім за ваші пояснення.
lepe

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
підкреслюють_

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 як приклад бітмаски
Niki

8

Не можна з перерахунком. Можливо, перерахунок не найкраще підходить для вашої ситуації.

Загальна умова - називати останнє значення перерахунку чимось на зразок 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;
    }

Він може використовуватися для повторення назад і вперед за допомогою безпідписаних цілих чисел, перерахунків і знаків:

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


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

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

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 :: inicijalizer_list <Item>. посилання .
marski

2

Якщо вам не подобається забруднювати перерахунок остаточним елементом COUNT (тому, що, можливо, якщо ви також використовуєте enum в комутаторі, тоді компілятор попередить вас про відсутній випадок 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

У книзі мов програмування Bjarne Stroustrup на 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)
підкреслити_15

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 - просто створити "маску" для
вписаних

Якщо не змінилося наconstexpr static const int enumItems[]
Мохаммед Канан

0

У C ++ немає інтроспекції, тому ви не можете визначити подібну річ під час виконання.


1
Чи не могли б ви пояснити мені, чому потрібна "самоаналіз", щоб переглядати перерахунок?
Джонатан Мі

Може, термін - Рефлексія ?
kͩeͣmͮpͥ ͩ

2
Я намагаюся сказати 2 речі: 1) За багатьма іншими відповідями C ++ може це досягти, тому якщо ви збираєтесь сказати, що це не може, потрібне посилання або додаткове уточнення. 2) У його теперішній формі це в кращому випадку коментар, звичайно не відповідь.
Джонатан Мі

Тоді скажіть мою відповідь - я думаю, ви це більш ніж виправдали
kͩeͣmͮpͥ ͩ

1
Я знову наб'юсь у 2 коментарях: 1) Я не підкреслюю, тому що я вважаю, що прийом пониження демонструє участь сайту, я вважаю це контрпродуктивним 2) Я все ще не розумію, що ви намагаєтесь сказати, але це звучить як ти розумієш щось, чого я не знаю. Я вважаю за краще, щоб ти детальніше розробив, а не видаляв відповідь.
Джонатан Мі

0

Якби ви знали, що значення enum є послідовними, наприклад, Qt: Key enum, ви можете:

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

Просто зробіть масив ints та циклічні масиви над масивом, але змусьте останній елемент сказати -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 перевірка не зіткнеться з одним із елементів курсу.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.