wake-up-neo.com

Wie kann ich eine Enumeration durchlaufen?

Mir ist gerade aufgefallen, dass Sie keine mathematischen Standardoperatoren für Enumerationen wie ++ oder + = verwenden können

Was ist also der beste Weg, um alle Werte in einer C++ - Enumeration zu durchlaufen?

252
Adam

Der typische Weg ist wie folgt:

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

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

Natürlich bricht dies zusammen, wenn die Aufzählungswerte angegeben werden:

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

Dies zeigt, dass eine Aufzählung nicht wirklich durchlaufen werden soll. Eine typische Methode, um mit einer Enumeration umzugehen, ist die Verwendung in einer switch-Anweisung.

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

Wenn Sie wirklich auflisten möchten, füllen Sie die Aufzählungswerte in einen Vektor und iterieren Sie darüber. Dadurch werden auch die angegebenen Aufzählungswerte korrekt behandelt.

229
andreas buykx
#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;
}
32
ZDF

Wenn Ihre Aufzählung mit 0 beginnt und das Inkrement immer 1 ist.

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

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

Wenn nicht, denke ich, ist das einzige, warum so etwas wie ein

vector<enumType> vEnums;

fügen Sie die Elemente hinzu und verwenden Sie normale Iteratoren ....

18
João Augusto

Mit c ++ 11 gibt es tatsächlich eine Alternative: Schreiben eines einfachen templatisierten benutzerdefinierten Iterators.

nehmen wir an, Ihre Aufzählung ist

enum class foo {
  one,
  two,
  three
};

Dieser generische Code wird den Trick recht effizient erledigen - in einem generischen Header platzieren, dient er Ihnen für jedes Enum, das Sie eventuell wiederholen müssen:

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

Sie müssen es spezialisieren

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

Und dann können Sie mit range-for iterieren

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

Die Annahme, dass Sie keine Lücken in Ihrem Enum haben, ist immer noch wahr. Es gibt keine Annahme über die Anzahl der Bits, die tatsächlich zum Speichern des Aufzählungswerts benötigt werden (dank std :: Underlying_type).

13

zu viel kompliziert diese lösung, ich mag das:

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

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

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

Sie können nicht mit einer Aufzählung. Vielleicht passt eine Aufzählung nicht zu Ihrer Situation.

Eine übliche Konvention ist, den letzten Aufzählungswert in etwa MAX zu benennen und damit eine Schleife mit einem int zu steuern.

8
Corey Trager

Etwas, das in den anderen Antworten nicht behandelt wurde = Wenn Sie stark typisierte C++ 11-Enumerationen verwenden, können Sie ++ oder + int nicht für sie verwenden. In diesem Fall ist etwas unordentlicher Lösung erforderlich:

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)

}
6
Riot

Sie können das folgende Makro versuchen und definieren:

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

Jetzt können Sie es verwenden:

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

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

Es kann verwendet werden, um durch vorzeichenlose, ganze Zahlen, Enumerationen und Zeichen vor und zurück zu iterieren:

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


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

Trotz seiner umständlichen Definition ist es sehr gut optimiert. Ich habe mir Disassembler in VC++ angesehen .. Der Code ist extrem effizient. Lassen Sie sich nicht scheuen, sondern die drei for-Anweisungen: Der Compiler erzeugt nach der Optimierung nur eine Schleife! Sie können sogar geschlossene Schleifen definieren:

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

Sie können offensichtlich nicht durch aufgezählte Typen mit Lücken iterieren. 

6
Mikhail Semenov

Ich mache das oft so

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

oder wenn nicht aufeinanderfolgend, aber mit einem regelmäßigen Schritt (z. B. Bitflags)

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

    for (EAnimali = E_First; i < E_Last; i = EAnimal(i << 1))
    {}
6
Niki

Sie können auch die Inkrement-/Dekrement-Operatoren für Ihren Aufzählungstyp überladen.

4
JohnMcG

Wenn Sie Ihre Aufzählung nicht mit einem endgültigen COUNT-Eintrag verschmutzen möchten (wenn Sie möglicherweise auch die Aufzählung in einem Schalter verwenden, wird der Compiler Sie dann über einen fehlenden Fall COUNT :) warnen:

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
Niels Holst

Für MS-Compiler:

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

Hinweis: Dies ist viel weniger Code als die Antwort des einfachen benutzerdefinierten Iterators.

Sie können dies mit GCC erreichen, indem Sie typeof anstelle von decltype verwenden, aber ich kann den Compiler im Moment nicht zur Hand haben, um sicherzustellen, dass er kompiliert wird.

1
user2407277

Angenommen, die Aufzählung wird fortlaufend nummeriert, ist fehleranfällig. Darüber hinaus möchten Sie möglicherweise nur ausgewählte Enumeratoren durchlaufen. Wenn diese Teilmenge klein ist, kann es eine elegante Wahl sein, sie explizit zu durchlaufen:

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

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

Hier ist eine weitere Lösung, die nur für zusammenhängende Enums funktioniert. Es gibt die erwartete Iteration, mit Ausnahme der Hässlichkeit im Inkrement, zu der es gehört, da dies in C++ defekt ist.

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
Ethan Bradford

Wenn Sie wüssten, dass die Aufzählungswerte sequentiell sind, beispielsweise die Aufzählung "Qt: Key", könnten Sie

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

Es funktioniert wie erwartet.

0
kcrossen
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]
}
0
Justin Moloney

In C++ gibt es keine Introspektion, daher können Sie diese Art von Daten zur Laufzeit nicht ermitteln.

0
David Kemp
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);
}

Ein constexpr std::array kann auch nicht sequentielle Aufzählungen durchlaufen, ohne dass das Array vom Compiler instanziiert wird. Dies hängt von Dingen wie der Optimierungsheuristik des Compilers ab und davon, ob Sie die Adresse des Arrays verwenden.

In meinen Experimenten habe ich festgestellt, dass g++ 9.1 mit -O3 das obige Array wegoptimiert, wenn es 2 nicht-sequentielle Werte oder einige sequentielle Werte gibt (ich habe bis zu 6 getestet). Dies funktioniert jedoch nur, wenn Sie eine if -Anweisung haben. (Ich habe eine Anweisung ausprobiert, die einen ganzzahligen Wert verglichen hat, der größer als alle Elemente in einem sequenziellen Array ist, und die Iteration eingezeichnet, obwohl keine ausgeschlossen wurde. Als ich die if-Anweisung wegließ, wurden die Werte in den Speicher gestellt.) Außerdem wurde 5 eingezeichnet Werte aus einer nicht sequentiellen Aufzählung in [einem Fall | https://godbolt.org/z/XuGtoc] . Ich vermute, dass dieses merkwürdige Verhalten auf eine tiefe Heuristik zurückzuführen ist, die mit Caches und der Vorhersage von Verzweigungen zu tun hat.

Hier ist ein Link zu einer einfachen Testiteration auf Godbolt der demonstriert, dass das Array nicht immer instanziiert wird.

Der Preis dieser Technik besteht darin, die Enum-Elemente zweimal zu schreiben und die beiden Listen synchron zu halten.

0
Eponymous