Comment puis-je répéter sur une énumération?


304

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

Alors, quelle est la meilleure façon d'itérer à travers toutes les valeurs dans une énumération C ++?


2
L'une des nombreuses approches: Lorsque l'énumération ne suffit pas: classes d'énumération pour C ++ . Et, si vous voulez quelque chose de plus encapsulé, essayez cette approche de James Kanze.
Don Wakefield

Les éléments liés ont des réponses intéressantes.
Tony

Ces réponses ne semblent pas couvrir le problème qui intn'est peut-être pas assez important! ( [C++03: 7.2/5])
Courses de légèreté en orbite le

Fait intéressant, vous pouvez définir des operator++énumérations; cependant, vous pouvez donc le faire for(Enum_E e = (Enum_E)0; e < ENUM_COUNT; e++). Notez que vous devez effectuer un cast 0vers Enum_Ecar C ++ interdit les opérateurs d'affectation sur les énumérations.
weberc2

S'il y avait un opérateur de compilation, similaire à la façon dont sizeof fonctionne, qui pouvait émettre un littéral std :: initializer_list comprenant les valeurs de l'énumération, nous aurions une solution et n'impliquerions aucun temps d'exécution.
jbruni

Réponses:


263

La manière typique est la suivante:

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

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

Veuillez noter que l'énumération Lastest destinée à être ignorée par l'itération. En utilisant cette "fausse" Lasténumération, vous n'avez pas à mettre à jour votre condition de terminaison dans la boucle for à la dernière "vraie" énumération chaque fois que vous souhaitez ajouter une nouvelle énumération. Si vous souhaitez ajouter d'autres énumérations plus tard, ajoutez-les simplement avant Last. La boucle dans cet exemple fonctionnera toujours.

Bien sûr, cela tombe en panne si les valeurs d'énumération 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 à parcourir. La manière typique de traiter une énumération consiste à 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, remplissez les valeurs d'énumération dans un vecteur et parcourez-le. Cela traitera également correctement les valeurs d'énumération spécifiées.


13
Notez que, dans la première partie de l'exemple, si vous souhaitez utiliser 'i' comme une énumération Foo et non un int, vous devrez le convertir statiquement comme: static_cast <Foo> (i)
Clayton

5
Vous sautez également Last in the loop. Doit être <= dernier
Tony

20
@Tony Last est censé être ignoré. Si vous souhaitez ajouter d'autres énumérations plus tard, ajoutez-les avant Last ... la boucle du premier exemple fonctionnera toujours. En utilisant une "fausse" dernière énumération, vous n'avez pas à mettre à jour votre condition de terminaison dans la boucle for vers la dernière "vraie" énumération chaque fois que vous souhaitez ajouter une nouvelle énumération.
timidpueo

Sauf que maintenant vous avez réellement alloué de la mémoire quand une énumération, à condition qu'elle soit indexée zéro et strictement continue, peut faire cette tâche sans allouer de mémoire.
Cloud

1
Notez que pour que cette définition d'énumération soit sûre pour les mises à jour, il faut définir une valeur UNKNOWN = 0. De plus, je suggérerais simplement de supprimer le defaultcas lors du basculement des valeurs d'énumération car cela pourrait masquer les cas où la gestion des valeurs a été oubliée jusqu'à l'exécution. Au lieu de cela, vous devez coder en dur toutes les valeurs et utiliser le UNKNOWNchamp pour détecter les incompatibilités.
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;
}

Merci! Notez que si vous traversez des fichiers / classes et si la compatibilité MS vous pose des problèmes avec les constantes non entières déclarées par l'en-tête, cela aide sous mon compilateur à mettre explicitement la taille dans le type dans l'en-tête: static const Type All[3];et alors je suis capable pour initialiser dans la source: const MyEnum::Type MyEnum::All[3] = { a, b, c }; Avant de faire cela, je recevais des Error in range-based for...erreurs odieuses (parce que le tableau avait une taille inconnue). Compris cela grâce à une réponse connexe
sage

1
La version tableau est très conviviale de copier-coller. La réponse la plus satisfaisante est d'ailleurs "NON" ou "uniquement pour séquentiel". Probablement même compatible avec les macros.
Paulo Neves

1
cela peut être une bonne solution pour les énumérations avec un petit nombre d'éléments, mais pour les énumérations avec un grand nombre d'éléments, il ne doit pas bien s'adapter.
kato2

20

Si votre énumération commence par 0 et 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;

ajouter les éléments et utiliser des itérateurs normaux ....


19

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

supposons que votre énumération soit

enum class foo {
  one,
  two,
  three
};

Ce code générique fera l'affaire, assez efficacement - placé dans un en-tête générique, il vous servira pour toute énumération que vous pourriez avoir besoin de parcourir:

#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 devrez 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 parentheses!
   do_stuff(i);
}

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


1
@lepe? Vous créez simplement un typedef différent pour une énumération différente.
Andrew Lazarus

2
@lepe C'est comme dire que ce std::vectorn'est pas générique parce que std::vector<foo>c'est lié à foo.
Kyle Strand

1
typedef Iterator<color, color::green, color::red> colorIterator;Assurez-vous de comprendre le fonctionnement des instanciations de modèle.
Andrew Lazarus

2
Oh, je vois le problème - foo operator*() { ...devrait l'être C operator*() { ....
Kyle Strand

1
@KyleStrand: Vous l'avez compris! cela fait tout à fait sens maintenant. Le code doit-il être mis à jour? Merci à tous pour vos explications.
lepe

16

trop compliqué ces solution, j'aime ça:

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

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

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

Je ne sais pas pourquoi cela a été rejeté. C'est une solution raisonnable.
Paul Brannan

11
Je pense que c'est parce que les entrées doivent être conservées à deux endroits.
Ant

C ++ permet-il la for (NodePosition pos : NodePositionVector)syntaxe? Pour autant que je sache, il s'agit de la syntaxe Java, et vous aurez besoin d'itérateurs en C ++ pour faire quelque chose d'équivalent.
thegreatjedi

3
@thegreatjedi Depuis C ++ 11, vous pouvez, encore plus simplement: for (auto pos: NodePositionVector) {..}
Enzojz

@thegreatjedi Il aurait été plus rapide de rechercher, voire de compiler un programme de test, que de poser cette question. Mais oui, depuis C ++ 11, c'est une syntaxe C ++ parfaitement valide, que le compilateur traduit en code équivalent (et beaucoup plus verbeux / moins abstrait), généralement via des itérateurs; voir cppreference . Et, comme l'a dit Enzojz, C ++ 11 a également ajouté auto, vous n'avez donc pas à déclarer explicitement le type des éléments, sauf si vous (A) devez utiliser un opérateur de conversion ou (B) n'aimez pas autopour une raison quelconque . La plupart des forutilisateurs de la autogamme utilisent AFAICT
underscore_d

9

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 sinon successif, mais avec pas régulier (par exemple drapeaux de bits)

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

mais, à quoi sert d'imprimer les valeurs bit par bit?
Anu

1
Pour utiliser des énumérations pour créer des masques de bits. Par exemple, combinez plusieurs options dans une seule variable, puis utilisez le FOR pour tester chaque option. Correction de mon message avec un meilleur exemple.
Niki

Je n'arrive toujours pas à l'utiliser (et votre message montre toujours l'ancien exemple)! Utiliser enum comme bitmasks est vraiment utile, mais n'a pas pu connecter les points! pourriez-vous s'il vous plaît élaborer un peu dans votre exemple en détail, vous pouvez également mettre le code supplémentaire.
Anu

@anu Désolé, vous n'avez pas vu votre commentaire. Ajout de la classe Frog comme exemple de masque de bits
Niki

8

Vous ne pouvez pas avec une énumération. Peut-être qu'un enum n'est pas le mieux adapté à votre situation.

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


Il existe plusieurs exemples ici qui démontrent le contraire. Dans votre propre déclaration, vous vous contredisez (deuxième ligne).
Niki

6

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 les utiliser ++ni les utiliser + int. Dans ce cas, une solution un peu plus compliquée 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)

}

3
... mais C ++ 11 introduit la plage basée sur cela qui est indiquée dans d'autres réponses. :-)
sage

5

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

Vous pouvez maintenant 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 avant et en arrière à travers des entiers, des énumérations et des caractères non signés:

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


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

Malgré sa définition maladroite, il est très bien optimisé. J'ai regardé le désassembleur dans VC ++. Le code est extrêmement efficace. Ne vous découragez pas mais les trois pour les instructions: le compilateur ne produira qu'une seule 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.


1
C'est un merveilleux hack! Bien qu'il soit plus approprié pour C que pour C ++, on pourrait dire.
einpoklum

3
_A1n'est pas un nom autorisé, c'est un trait de soulignement principal avec une majuscule suivante.
Martin Ueding

3

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


1
Vous ne pouvez surcharger aucun opérateur sur les types énumérés C ou C ++. À moins que vous ne deviez créer une structure / classe qui émule une énumération de valeurs.
Trevor Hickey

2
C ++ permet de surcharger les opérateurs sur les énumérations. Voir stackoverflow.com/questions/2571456/… .
Arch D. Robison

Surcharger l'incrément / décrément nécessite de prendre une décision sur ce qu'il faut faire en cas de débordement
Éponyme

3

En supposant que l'énumération est numérotée séquentiellement, les erreurs sont sujettes. En outre, vous souhaiterez peut-être parcourir les énumérateurs sélectionnés uniquement. Si ce sous-ensemble est petit, le boucler explicitement pourrait être un choix élégant:

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

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

C'est une belle option, je pense. Doit faire partie d'une spécification C ++ plus récente que celle que j'utilisais lorsque j'ai posé la question que je devine?
Adam

Oui. Il itère sur une liste std :: initializer_list <Item>. lien .
marski

2

Si vous n'aimez pas polluer votre énumération avec un élément COUNT final (car peut-être que si vous utilisez également l'énumération dans un commutateur, le compilateur vous avertira d'un cas manquant COUNT :), vous pouvez le faire:

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

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, qui est à sa place, car 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
L'incrémentation peut être raccourcie foo = Bar(foo + 1).
HolyBlackCat

Merci, HolyBlackCat, j'ai incorporé votre excellente suggestion! Je remarque également que Riot a à peu près cette même solution, mais conforme à un typage fort (et donc plus verbeux).
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);
}

UNE 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 de choses 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 -O3optimisera le tableau ci-dessus s'il y a 2 valeurs non séquentielles ou pas mal de valeurs séquentielles (j'ai testé jusqu'à 6). Mais il ne fait cela que si vous avez une ifdéclaration. (J'ai essayé une instruction qui comparait une valeur entière supérieure à tous les éléments d'un tableau séquentiel et elle a aligné l'itération bien qu'aucun ne soit exclu, mais quand j'ai omis l'instruction if, les valeurs ont été mises en mémoire.) Elle a également inséré 5 valeurs 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 aux caches et à la prédiction de branche.

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.


J'aime la sémantique simple de boucle de type plage et je pense qu'elle évoluera encore plus, c'est pourquoi j'aime cette solution.
rtischer8277

2

Dans le livre du langage de programmation C ++ de Bjarne Stroustrup, vous pouvez lire qu'il propose de surcharger le operator++pour votre spécifique enum. enumsont des types définis par l'utilisateur et un opérateur de surcharge existe dans le langage pour ces situations spécifiques.

Vous pourrez coder les éléments suivants:

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

code de test: http://cpp.sh/357gb

Attention que j'utilise enum class. Le code fonctionne bien enumaussi. Mais je préfère enum classcar ils sont fortement typés et peuvent nous empêcher de faire des erreurs au moment de la compilation.


Un vote négatif a été déposé sur ce post. Pourquoi ne répondrait-il pas à la question?
LAL

La raison en est probablement parce que c'est une terrible solution sur le plan architectural: elle vous oblige à écrire une logique globale destinée à un composant spécifique (votre énumération), de plus si votre énumération change pour une raison quelconque, vous êtes obligé de modifier votre + + opérateur aussi, comme une approche qui n'est durable pour aucun projet de moyenne à grande échelle, il n'est pas surprenant qu'elle provienne d'une recommandation de Bjarne Stroustrup, à l'époque l'architecture logicielle était comme de la science-fiction
Manjia

La question d'origine est d'avoir un opérateur sur un enum. Ce n'était pas une question architecturale. Je ne crois pas qu'en 2013 C ++ était une science-fiction.
LAL

1

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: c'est beaucoup moins de code que la réponse d'itérateur personnalisé simple.

Vous pouvez le faire fonctionner avec GCC en utilisant typeofau lieu de decltype, mais je n'ai pas ce compilateur à portée de main pour le moment pour être sûr qu'il compile.


Cela a été écrit ~ 5 ans après être decltypedevenu le C ++ standard, vous ne devriez donc pas recommander l' typeofancien du GCC. GCC vaguement récent gère decltypetrès bien. Il y a d'autres problèmes: les conversions de style C sont déconseillées et les macros pires. Les fonctionnalités C ++ appropriées peuvent donner les mêmes fonctionnalités génériques. Ce serait mieux réécrite à utiliser static_castet une fonction de modèle: template <typename T> auto inc_enum(T const t) { return static_cast<T>(static cast<int>(t) + 1); }. Et les lancers ne sont pas nécessaires pour les non enum class. Alternativement, les opérateurs peuvent être surchargés par enumtype (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]
}

Cette solution créera une variable inutilement statique en mémoire tandis que l'objectif d'énumération est juste de créer un «masque» aux constantes en ligne
TheArquitect

Sauf si changé enconstexpr static const int enumItems[]
Mohammad Kanan

0

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


1
Pourriez-vous m'expliquer pourquoi une "introspection" serait nécessaire pour parcourir une énumération?
Jonathan Mee

Peut-être que le terme est la réflexion ?
kͩeͣmͮpͥ

2
J'essaie de dire 2 choses: 1) Pour de nombreuses autres réponses, C ++ peut accomplir cela, donc si vous dites qu'il ne le peut pas, un lien ou des clarifications supplémentaires sont nécessaires. 2) Dans sa forme actuelle, c'est au mieux un commentaire, certainement pas une réponse.
Jonathan Mee

Votez ma réponse alors - je pense que vous l'avez plus que justifiée
kͩeͣmͮpͥ

1
Je vais encore entasser 2 commentaires: 1) Je ne vote pas parce que je trouve que recevoir un vote négatif démotive la participation au site, je trouve cela contre-productif 2) Je ne comprends toujours pas ce que vous essayez de dire, mais cela ressemble à vous comprenez quelque chose que je ne comprends pas, auquel cas je préférerais que vous élaboriez plutôt que de supprimer une réponse votée.
Jonathan Mee

0

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.


-1

Faites simplement un tableau d'entiers et faites une boucle sur le tableau, mais faites le dernier élément dire -1 et utilisez-le pour la condition de sortie.

Si enum est:

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

puis créez un tableau:

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

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

Cela ne se décompose pas, peu importe les chiffres de la représentation, tant que le contrôle -1 ne heurte pas l'un des éléments, bien sûr.

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.