web-dev-qa-db-fra.com

Comment puis-je parcourir une enum?

Je viens de remarquer que vous ne pouvez pas utiliser d'opérateurs mathématiques standard sur une énumération telle que ++ ou + =

Quel est donc le meilleur moyen de parcourir toutes les valeurs d’un enum C++?

252
Adam

La manière typique est comme suit:

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

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

Bien sûr, cela s’écroule si les valeurs enum sont spécifiées:

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

Cela illustre le fait qu’une énumération n’est pas vraiment destinée à être parcourue. La manière typique de traiter une énumération est de l’utiliser dans une instruction switch.

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

Si vous voulez vraiment énumérer, insérez les valeurs énumérées dans un vecteur et parcourez-les par-dessus. Cela traitera également correctement les valeurs enum spécifiées.

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

Si votre enum commence par 0 et que l'incrément est toujours 1.

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

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

Sinon, je suppose que le seul pourquoi est de créer quelque chose comme un

vector<enumType> vEnums;

ajoutez les éléments et utilisez des itérateurs normaux ....

18
João Augusto

Avec c ++ 11, il existe une alternative: écrire un simple itérateur personnalisé basé sur un modèle.

supposons que votre enum est

enum class foo {
  one,
  two,
  three
};

Ce code générique fera l'affaire, assez efficacement - placez-le dans un en-tête générique, il vous servira pour toute énumération dont vous aurez besoin pour effectuer une itération sur:

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

Vous aurez besoin de le spécialiser

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

Et puis vous pouvez itérer en utilisant range-for

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

L'hypothèse selon laquelle vous n'avez pas de lacunes dans votre enum est toujours vraie; il n'y a aucune hypothèse sur le nombre de bits réellement nécessaires pour stocker la valeur enum (grâce à std :: sous_type)

13
Francesco Chemolli

trop compliqué ces solution, je fais comme ça:

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

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

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

Vous ne pouvez pas avec un enum. Peut-être qu'une énumération n'est pas la meilleure solution pour votre situation.

Une convention courante consiste à nommer la dernière valeur enum quelque chose comme MAX et à l'utiliser pour contrôler une boucle utilisant un int.

8
Corey Trager

Quelque chose qui n’a pas été couvert dans les autres réponses = si vous utilisez des énumérations C++ 11 fortement typées, vous ne pouvez pas utiliser ++ ou + int sur elles. Dans ce cas, une solution un peu plus complexe est requise:

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

Vous pouvez essayer de définir la macro suivante:

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

Maintenant vous pouvez l'utiliser:

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

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

Il peut être utilisé pour parcourir en arrière et en avant à travers unsigned, integers, enums et chars:

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


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

Malgré sa définition délicate, il est très bien optimisé. J'ai regardé désassembleur dans VC++… .. Le code est extrêmement efficace. Ne soyez pas rebutés mais les trois pour les déclarations: le compilateur ne produira qu'une boucle après optimisation! Vous pouvez même définir des boucles fermées:

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

Vous ne pouvez évidemment pas parcourir les types énumérés avec des lacunes. 

6
Mikhail Semenov

Je le fais souvent comme ça

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

ou si pas successifs, mais avec pas régulier (par exemple, drapeaux de bits)

    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

Vous pouvez également surcharger les opérateurs d'incrémentation/décrémentation pour votre type énuméré.

4
JohnMcG

Si vous n'aimez pas polluer votre enum avec un dernier élément COUNT (parce que si vous utilisez également l'énum dans un commutateur, le compilateur vous avertira alors qu'il manque un cas 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
Niels Holst

Pour les compilateurs 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); 
}

Remarque: il s'agit bien moins de code que la réponse par itérateur personnalisé personnalisée basée sur un modèle.

Vous pouvez obtenir que cela fonctionne avec GCC en utilisant typeof au lieu de decltype, mais je n'ai pas ce compilateur à portée de la main pour être sûr qu'il compile.

1
user2407277

En supposant que l’énumération soit numérotée séquentiellement, elle est sujette aux erreurs. De plus, vous souhaiterez peut-être effectuer une itération sur les énumérateurs sélectionnés uniquement. Si ce sous-ensemble est petit, une boucle explicite sur celui-ci peut constituer un choix élégant:

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

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

Voici une autre solution qui ne fonctionne que pour les énumérations contiguës. Il donne l'itération attendue, à l'exception de la laideur dans l'incrément, auquel il appartient, puisque c'est ce qui est cassé en 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
Ethan Bradford

Si vous saviez que les valeurs d'énumération étaient séquentielles, par exemple l'énumération Qt: Key, vous pourriez:

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

Cela fonctionne comme prévu.

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

C++ n'a pas d'introspection, vous ne pouvez donc pas déterminer ce genre de chose au moment de l'exécution.

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

Un constexpr std::array peut itérer même des énumérations non séquentielles sans que le tableau soit instancié par le compilateur. Cela dépend d'éléments comme l'heuristique d'optimisation du compilateur et si vous prenez l'adresse du tableau.

Dans mes expériences, j'ai trouvé que g++ 9.1 avec -O3 optimisera le tableau ci-dessus s'il existe 2 valeurs non séquentielles ou plusieurs valeurs séquentielles (j'ai testé jusqu'à 6). Mais cela ne le fait que si vous avez une instruction if. (J'ai essayé une instruction comparant une valeur entière supérieure à tous les éléments d'un tableau séquentiel. Elle insère l'itération en ligne, même si aucune n'est exclue, mais lorsque j'ai omis l'instruction if, les valeurs ont été mises en mémoire.) 5 valeurs provenant d'une énumération non séquentielle dans [un cas | https://godbolt.org/z/XuGtoc] . Je soupçonne que ce comportement étrange est dû à une heuristique profonde liée à la prédiction de caches et de branches.

Voici un lien vers une simple itération de test sur godbolt qui montre que le tableau n'est pas toujours instancié.

Le prix de cette technique est d'écrire deux fois les éléments enum et de synchroniser les deux listes.

0
Eponymous