Come posso iterare su un enum?


304

Ho appena notato che non è possibile utilizzare operatori matematici standard su un enum come ++ o + =

Quindi qual è il modo migliore per scorrere tutti i valori in un enumerazione C ++?


2
Uno dei tanti approcci: quando enum Just Isn't Enough: classi di enumerazione per C ++ . E, se vuoi qualcosa di più incapsulato, prova questo approccio di James Kanze.
Don Wakefield,

Gli articoli collegati hanno alcune risposte interessanti.
Tony,

Queste risposte non sembrano coprire il problema che intpotrebbe non essere abbastanza grande! ( [C++03: 7.2/5])
Razze di leggerezza in orbita

È interessante notare che è possibile definire operator++enumerazioni; tuttavia, così puoi farlo for(Enum_E e = (Enum_E)0; e < ENUM_COUNT; e++). Nota che devi fusione 0a Enum_Ecausa operatori di assegnazione proibisce C ++ su enumerazioni.
weberc2,

Se esistesse un operatore di tempo di compilazione, simile al modo in cui sizeof funziona, che potrebbe emettere un valore letterale std :: initializer_list comprendente i valori dell'enum, avremmo una soluzione e non comporterebbe alcun sovraccarico di runtime.
jbruni,

Risposte:


263

Il modo tipico è il seguente:

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

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

Si noti che l'enum Lastdeve essere ignorato dall'iterazione. Utilizzando questo Lastenum "falso" , non è necessario aggiornare la propria condizione di terminazione nel ciclo for all'ultimo enum "reale" ogni volta che si desidera aggiungere un nuovo enum. Se vuoi aggiungere più enumerazioni in un secondo momento, aggiungile prima di Last. Il ciclo in questo esempio continuerà a funzionare.

Naturalmente, questo si interrompe se vengono specificati i valori enum:

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

Ciò dimostra che un enum non è realmente destinato a scorrere. Il modo tipico di gestire un enum è usarlo in un'istruzione switch.

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

Se vuoi davvero enumerare, riempi i valori enum in un vettore e ripeti quello. Questo tratterà correttamente anche i valori enum specificati.


13
Nota che, nella prima parte dell'esempio, se vuoi usare 'i' come enumerazione Foo e non un int, dovrai lanciarlo staticamente come: static_cast <Foo> (i)
Clayton

5
Inoltre stai saltando Last in the loop. Dovrebbe essere <= Last
Tony,

20
@Tony Last deve essere ignorato. Se vuoi aggiungere più enumerazioni in un secondo momento, aggiungile prima di Last ... il ciclo nel primo esempio continuerà a funzionare. Utilizzando un ultimo enum "falso", non è necessario aggiornare la propria condizione di terminazione nel ciclo for all'ultimo enum "reale" ogni volta che si desidera aggiungere un nuovo enum.
timidpueo

Tranne ora che hai effettivamente allocato memoria quando un enum, purché sia ​​a zero e rigorosamente continuo, può fare quel compito senza allocare memoria.
Cloud

1
Si noti che affinché questa definizione di enum sia sicura per gli aggiornamenti, è necessario definire un valore UNKNOWN = 0. Inoltre, suggerirei di abbandonare il defaultcaso quando si passa su valori enum poiché potrebbe nascondere casi in cui la gestione dei valori è stata dimenticata fino al runtime. Invece si dovrebbe hardcode tutti i valori e utilizzare il UNKNOWNcampo per rilevare incompatibilità.
Benjamin Bannier,

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

Grazie! Nota che se stai attraversando file / classi e se la compatibilità con MS ti dà problemi con costanti non integer dichiarate nell'intestazione, aiuta il mio compilatore a inserire esplicitamente la dimensione nel tipo nell'intestazione: static const Type All[3];e poi sono in grado per inizializzare nella fonte: const MyEnum::Type MyEnum::All[3] = { a, b, c }; prima di farlo, stavo ottenendo Error in range-based for...errori odiosi (perché l'array aveva una dimensione sconosciuta). L'ho capito grazie a una risposta correlata
saggio

1
La versione array è molto intuitiva per il copia e incolla. La risposta più soddisfacente, inoltre, "NO" o "solo per sequenziale". Probabilmente anche macro friendly.
Paulo Neves,

1
questa potrebbe essere una buona soluzione per enumerazioni con un numero limitato di elementi, ma per enumerazioni con un numero elevato di elementi non deve adattarsi bene.
kato2

20

Se la tua enum inizia con 0 e l'incremento è sempre 1.

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

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

Altrimenti credo che l'unico motivo sia creare qualcosa di simile a

vector<enumType> vEnums;

aggiungi gli elementi e usa i normali iteratori ....


19

Con c ++ 11 esiste effettivamente un'alternativa: scrivere un semplice iteratore personalizzato basato su modelli.

supponiamo che sia il tuo enum

enum class foo {
  one,
  two,
  three
};

Questo codice generico farà il trucco, in modo abbastanza efficiente - posizionalo in un'intestazione generica, ti servirà per qualsiasi enum di cui potresti aver bisogno per scorrere:

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

Dovrai specializzarlo

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

E poi puoi iterare usando range-for

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

L'ipotesi che tu non abbia lacune nel tuo enum è ancora vera; non si presuppone il numero di bit effettivamente necessari per memorizzare il valore enum (grazie a std :: sottostanti_tipo)


1
@lepe? Devi solo fare un typedef diverso per un enum diverso.
Andrew Lazarus,

2
@lepe È come dire che std::vectornon è generico perché std::vector<foo>è legato foo.
Kyle Strand,

1
typedef Iterator<color, color::green, color::red> colorIterator;Assicurati di capire come funzionano le istanze del modello.
Andrew Lazarus,

2
Oh, vedo il problema - foo operator*() { ...dovrebbe essere C operator*() { ....
Kyle Strand,

1
@KyleStrand: ce l'hai! questo ha perfettamente senso ora. Il codice deve essere aggiornato? Grazie a tutti per le vostre spiegazioni.
lepe il

16

troppo complicata questa soluzione, mi piace che:

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

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

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

Non so perché questo sia stato sottoposto a downgrade. È una soluzione ragionevole.
Paul Brannan,

11
Mi aspetto che sia perché le voci devono essere mantenute in due punti.
Formica

Il C ++ consente la for (NodePosition pos : NodePositionVector)sintassi? Per quanto ne so, questa è la sintassi Java, e per eseguire qualcosa di equivalente avrai bisogno di iteratori in C ++.
thegreatjedi il

3
@thegreatjedi Da C ++ 11 puoi, ancora più semplice: for (auto pos: NodePositionVector) {..}
Enzojz

@thegreatjedi Sarebbe stato più veloce cercare, o anche compilare un programma di test, che porre quella domanda. Ma sì, poiché C ++ 11 è una sintassi C ++ perfettamente valida, che il compilatore traduce nel codice equivalente (e molto più dettagliato / meno astratto), di solito tramite iteratori; vedi cppreference . E, come diceva Enzojz, anche C ++ 11 ha aggiunto auto, quindi non è necessario dichiarare esplicitamente il tipo di elementi, a meno che (A) non sia necessario utilizzare un operatore di conversione o (B) non ti piaccia autoper qualche motivo . La maggior parte degli forutenti della autogamma utilizza AFAICT
underscore_d

9

Lo faccio spesso così

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

o se non successivo, ma con passo regolare (ad es. bit flag)

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

ma a che serve stampare i valori in bit?
Anu

1
Per usare enum per creare maschere di bit. ad esempio, combinare diverse opzioni in una singola variabile, quindi utilizzare FOR per testare ogni opzione. Risolto il mio post con un esempio migliore.
Niki

Non riesco ancora a utilizzarlo (e il tuo post mostra ancora il vecchio esempio)! Usare enum come maschere di bit è davvero utile, ma non è stato in grado di collegare i punti! potresti per favore elaborare un po 'nel tuo esempio in dettaglio, puoi inserire anche il codice aggiuntivo.
Anu

@anu Spiacente non ho visto il tuo commento. Aggiunta la classe Frog come esempio di maschera di bit
Niki

8

Non puoi con un enum. Forse un enum non è la soluzione migliore per la tua situazione.

Una convenzione comune è quella di nominare l'ultimo valore enum qualcosa come MAX e usarlo per controllare un loop usando un int.


Ci sono molti esempi qui che dimostrano il contrario. Nella tua stessa affermazione ti stai contraddicendo (seconda riga).
Niki,

6

Qualcosa che non è stato trattato nelle altre risposte = se stai usando enumerazioni C ++ 11 fortemente tipizzate, non puoi usarle ++o + intsu di esse. In tal caso, è necessaria una soluzione un po 'più complicata:

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
... ma C ++ 11 introduce l'intervallo basato su quello che viene mostrato in altre risposte. :-)
saggio

5

Puoi provare a definire la seguente macro:

#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))

Ora puoi usarlo:

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

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

Può essere utilizzato per scorrere avanti e indietro tra unsigned, interi, enum e caratteri:

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


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

Nonostante la sua definizione scomoda, è ottimizzato molto bene. Ho osservato il disassemblatore in VC ++. Il codice è estremamente efficiente. Non rimandare ma le tre dichiarazioni: il compilatore produrrà solo un ciclo dopo l'ottimizzazione! Puoi persino definire loop chiusi:

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

Ovviamente non puoi iterare attraverso tipi enumerati con lacune.


1
È un trucco meraviglioso! Sebbene sia più appropriato per C che per C ++, si potrebbe dire.
einpoklum,

3
_A1non è un nome consentito, è un carattere di sottolineatura iniziale con la seguente lettera maiuscola.
Martin Ueding,

3

È inoltre possibile sovraccaricare gli operatori di incremento / decremento per il tipo elencato.


1
Non è possibile sovraccaricare alcun operatore sui tipi enumerati C o C ++. A meno che non si creasse una struttura / classe che emulasse un elenco di valori.
Trevor Hickey,

2
C ++ consente agli operatori di sovraccarico su enum. Vedi stackoverflow.com/questions/2571456/… .
Arch D. Robison

Il sovraccarico dell'incremento / decremento richiede una decisione su cosa fare in caso di tracimazione
Eponimo

3

Supponendo che l'enum sia numerato in sequenza, è soggetto a errori. Inoltre, potresti voler iterare solo su enumeratori selezionati. Se quel sottoinsieme è piccolo, passarci sopra esplicitamente potrebbe essere una scelta elegante:

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

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

Questa è una bella opzione, penso. Deve far parte di una specifica C ++ più recente di quella che stavo usando quando ho posto la domanda che indovino?
Adam,

Sì. Esegue un'iterazione su uno std :: initializer_list <Item>. link .
Marski,

2

Se non ti piace inquinare la tua enum con un oggetto COUNT finale (perché forse se usi anche l'enum in uno switch allora il compilatore ti avvertirà di un caso COUNT mancante :), puoi farlo:

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

Ecco un'altra soluzione che funziona solo per enigmi contigui. Fornisce l'iterazione prevista, ad eccezione della bruttezza nell'incremento, che è dove appartiene, dal momento che è ciò che è rotto in 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
L'incremento può essere ridotto a foo = Bar(foo + 1).
HolyBlackCat

Grazie, HolyBlackCat, ho incorporato il tuo eccellente suggerimento! Noto anche che Riot ha praticamente la stessa soluzione, ma conforme alla tipizzazione forte (e quindi più dettagliata).
Ethan Bradford,

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

UN constexpr std::array può iterare anche enumerazioni non sequenziali senza che l'array sia istanziato dal compilatore. Questo dipende da cose come l'euristica dell'ottimizzazione del compilatore e dal fatto che tu prenda l'indirizzo dell'array.

Nei miei esperimenti, ho scoperto che g++9.1 con -O3ottimizzerà l'array sopra se ci sono 2 valori non sequenziali o parecchi valori sequenziali (ho testato fino a 6). Ma lo fa solo se hai una ifdichiarazione. (Ho provato un'istruzione che ha confrontato un valore intero maggiore di tutti gli elementi in un array sequenziale e ha incorporato l'iterazione nonostante nessuno sia stato escluso, ma quando ho lasciato fuori l'istruzione if, i valori sono stati messi in memoria.) Ha anche sottolineato 5 valori da un enum non sequenziale in [un caso | https://godbolt.org/z/XuGtoc] . Sospetto che questo strano comportamento sia dovuto alla profonda euristica che ha a che fare con la cache e la previsione del ramo.

Ecco un link a una semplice iterazione di prova su godbolt che dimostra che l'array non viene sempre istanziato.

Il prezzo di questa tecnica sta scrivendo gli elementi enum due volte e mantenendo sincronizzati i due elenchi.


Mi piace la semantica for-loop semplice come una gamma e penso che evolverà ancora di più, motivo per cui mi piace questa soluzione.
rtischer8277,

2

Nel libro del linguaggio di programmazione C ++ di Bjarne Stroustrup, puoi leggere che sta proponendo di sovraccaricare il operator++tuo specifico enum. enumsono tipi definiti dall'utente e l'operatore di overload esiste nella lingua per queste situazioni specifiche.

Sarai in grado di codificare quanto segue:

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

codice di prova: http://cpp.sh/357gb

Ricorda che sto usando enum class. Il codice funziona bene enumanche con . Ma preferisco enum classpoiché sono fortemente tipizzati e possono impedirci di fare errori in fase di compilazione.


Un downvote è stato lanciato su questo post. Qual è il motivo per cui non risponderebbe alla domanda?
LAL,

Il motivo è probabilmente perché questa è una terribile soluzione dal punto di vista architettonico: ti costringe a scrivere una logica di significato globale legata a un componente specifico (la tua enumerazione), inoltre se la tua enumerazione cambia per qualsiasi motivo sei costretto a modificare il tuo + + operatore anche, poiché un approccio non è sostenibile per qualsiasi progetto su scala medio-grande, non è una sorpresa che provenga da una raccomandazione di Bjarne Stroustrup, ai tempi l'architettura del software era come la fantascienza
Manjia

La domanda originale riguarda avere un operatore in un enum. Non era una domanda architettonica. Non credo che nel 2013 il C ++ fosse una fantascienza.
LAL

1

Per compilatori 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); 
}

Nota: questo è molto meno codice rispetto alla semplice risposta iteratrice personalizzata modellata.

Puoi farlo funzionare con GCC usando typeofinvece didecltype , ma al momento non ho quel compilatore a portata di mano per assicurarmi che si compili.


Questo è stato scritto ~ 5 anni dopo essere decltypediventato C ++ standard, quindi non dovresti raccomandare il vecchio obsoleto typeofGCC. GCC vagamente recente gestisce decltypebene. Ci sono altri problemi: i cast in stile C sono scoraggiati e le macro peggiori. Funzionalità C ++ appropriate possono fornire la stessa funzionalità generica. Questo sarebbe meglio riscritto per utilizzare static_caste una funzione template: template <typename T> auto inc_enum(T const t) { return static_cast<T>(static cast<int>(t) + 1); }. E i cast non sono necessari per i non enum class. In alternativa, gli operatori possono essere sovraccaricati per enumtipo (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]
}

Questa soluzione creerà una memoria inutilmente statica in memoria, mentre l'obiettivo di enum è solo quello di creare una "maschera" per le costanti in linea
TheArquitect,

Salvo modifica inconstexpr static const int enumItems[]
Mohammad Kanan

0

Il C ++ non ha introspezione, quindi non è possibile determinare questo tipo di cose in fase di esecuzione.


1
Potresti spiegarmi perché "introspezione" sarebbe necessaria per iterare su un enum?
Jonathan Mee,

Forse il termine è riflessione ?
kͩeͣmͮpͥ ͩ

2
Sto cercando di dire 2 cose: 1) Per molte altre risposte C ++ può farlo, quindi se hai intenzione di dire che non è possibile, è necessario un link o ulteriori chiarimenti. 2) Nella sua forma attuale questo è nella migliore delle ipotesi un commento, certamente non una risposta.
Jonathan Mee,

Votate la mia risposta allora - penso che abbiate più che giustificato
kͩeͣmͮpͥ ͩ

1
Mi riempirò di nuovo in 2 commenti: 1) Non declassare perché trovo che ricevere un declassamento demotiva la partecipazione del sito, trovo controproducente 2) Non capisco ancora cosa stai cercando di dire ma sembra capisci qualcosa che non capisco, nel qual caso preferirei che elaborassi piuttosto che eliminare una risposta declassata.
Jonathan Mee,

0

Se sapessi che i valori di enum erano sequenziali, ad esempio l'enumerazione Qt: Key, potresti:

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

Funziona come previsto.


-1

Basta creare un array di ints e scorrere sull'array, ma fare in modo che l'ultimo elemento dica -1 e utilizzarlo per la condizione di uscita.

Se enum è:

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

quindi creare un array:

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

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

Ciò non si interrompe, indipendentemente dagli ints nella rappresentazione, purché il controllo -1 non si scontri con uno degli elementi ovviamente.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.