Énumérer sur un enum en C++

en C++, est-il possible d'énumérer sur un enum (runtime ou compiler time (preferred)) et d'appeler des fonctions/générer du code pour chaque itération?

exemple de cas d'utilisation:

enum abc
{    
    start
    a,
    b,
    c,
    end
}    
for each (__enum__member__ in abc)
{    
    function_call(__enum__member__);    
}

duplicata Plausible:

38
demandé sur Community 2009-09-07 23:35:06

9 réponses

pour ajouter à @StackedCrooked réponse, vous pouvez surcharger operator++ , operator-- et operator* et avoir une fonctionnalité de type itérateur.

enum Color {
    Color_Begin,
    Color_Red = Color_Begin,
    Color_Orange,
    Color_Yellow,
    Color_Green,
    Color_Blue,
    Color_Indigo,
    Color_Violet,
    Color_End
};

namespace std {
template<>
struct iterator_traits<Color>  {
  typedef Color  value_type;
  typedef int    difference_type;
  typedef Color *pointer;
  typedef Color &reference;
  typedef std::bidirectional_iterator_tag
    iterator_category;
};
}

Color &operator++(Color &c) {
  assert(c != Color_End);
  c = static_cast<Color>(c + 1);
  return c;
}

Color operator++(Color &c, int) {
  assert(c != Color_End); 
  ++c;
  return static_cast<Color>(c - 1);
}

Color &operator--(Color &c) {
  assert(c != Color_Begin);
  return c = static_cast<Color>(c - 1);
}

Color operator--(Color &c, int) {
  assert(c != Color_Begin); 
  --c;
  return static_cast<Color>(c + 1);
}

Color operator*(Color c) {
  assert(c != Color_End);
  return c;
}

essayons avec quelque <algorithm> modèle

void print(Color c) {
  std::cout << c << std::endl;
}

int main() {
  std::for_each(Color_Begin, Color_End, &print);
}

maintenant, Color est un itérateur bidirectionnel constant. Voici une classe réutilisable j'ai codé tout en le faisant manuellement ci-dessus. J'ai remarqué qu'il pourrait fonctionner pour beaucoup plus d'énums, donc répéter la même le code à nouveau est assez fastidieux

// Code for testing enum_iterator
// --------------------------------

namespace color_test {
enum Color {
  Color_Begin,
  Color_Red = Color_Begin,
  Color_Orange,
  Color_Yellow,
  Color_Green,
  Color_Blue,
  Color_Indigo,
  Color_Violet,
  Color_End
};

Color begin(enum_identity<Color>) {
  return Color_Begin;
}

Color end(enum_identity<Color>) {
  return Color_End;
}
}

void print(color_test::Color c) {
  std::cout << c << std::endl;
}

int main() {
  enum_iterator<color_test::Color> b = color_test::Color_Begin, e;
  while(b != e)
    print(*b++);
}

la mise en œuvre suit.

template<typename T>
struct enum_identity { 
  typedef T type; 
};

namespace details {
void begin();
void end();
}

template<typename Enum>
struct enum_iterator 
  : std::iterator<std::bidirectional_iterator_tag, 
                  Enum> {
  enum_iterator():c(end()) { }

  enum_iterator(Enum c):c(c) { 
    assert(c >= begin() && c <= end());
  }

  enum_iterator &operator=(Enum c) {
    assert(c >= begin() && c <= end());
    this->c = c; 
    return *this;
  }

  static Enum begin() {
    using details::begin; // re-enable ADL
    return begin(enum_identity<Enum>());
  }

  static Enum end() {
    using details::end; // re-enable ADL
    return end(enum_identity<Enum>());
  }

  enum_iterator &operator++() {
    assert(c != end() && "incrementing past end?");
    c = static_cast<Enum>(c + 1);
    return *this;
  }

  enum_iterator operator++(int) {
    assert(c != end() && "incrementing past end?");
    enum_iterator cpy(*this);
    ++*this;
    return cpy;
  }

  enum_iterator &operator--() {
    assert(c != begin() && "decrementing beyond begin?");
    c = static_cast<Enum>(c - 1);
    return *this;
  }

  enum_iterator operator--(int) {
    assert(c != begin() && "decrementing beyond begin?");
    enum_iterator cpy(*this);
    --*this;
    return cpy;
  }

  Enum operator*() {
    assert(c != end() && "cannot dereference end iterator");
    return c;
  }

  Enum get_enum() const {
    return c;
  }

private:
  Enum c;
};

template<typename Enum>
bool operator==(enum_iterator<Enum> e1, enum_iterator<Enum> e2) {
  return e1.get_enum() == e2.get_enum();
}

template<typename Enum>
bool operator!=(enum_iterator<Enum> e1, enum_iterator<Enum> e2) {
  return !(e1 == e2);
}
54
répondu Johannes Schaub - litb 2009-09-08 02:17:04

C++ ne fournit pas actuellement d'itération d'énumérateur. Malgré cela, le besoin se fait parfois sentir. Une solution courante consiste à ajouter des valeurs qui marquent le début et la fin. Par exemple:

enum Color
{
    Color_Begin,
    Color_Red = Color_Begin,
    Color_Orange,
    Color_Yellow,
    Color_Green,
    Color_Blue,
    Color_Indigo,
    Color_Violet,
    Color_End
};

void foo(Color c)
{
}


void iterateColors()
{
    for (size_t colorIdx = Color_Begin; colorIdx != Color_End; ++colorIdx)
    {
        foo(static_cast<Color>(colorIdx));
    }
}
42
répondu StackedCrooked 2017-09-09 02:29:45

n'est possible sans un peu de travail manuel. Beaucoup de travail peut être fait par macros, si vous êtes prêt à creuser dans ce domaine.

4
répondu Konrad Rudolph 2009-09-07 19:36:06

en S'appuyant sur ce que dit Konrad, un idiome possible dans le cas de "générer du code pour chaque itération" est d'utiliser un fichier inclus pour représenter l'énumération:

mes trucs.h:

#ifndef LAST_ENUM_ELEMENT
#define LAST_ENUM_ELEMENT(ARG) ENUM_ELEMENT(ARG)
#endif

ENUM_ELEMENT(foo)
ENUM_ELEMENT(bar)
LAST_ENUM_ELEMENT(baz)

// not essential, but most likely every "caller" should do it anyway...
#undef LAST_ENUM_ELEMENT
#undef ENUM_ELEMENT

enum.h:

// include guard goes here (but mystuff.h doesn't have one)

enum element {
    #define ENUM_ELEMENT(ARG) ARG,
    #define LAST_ENUM_ELEMENT(ARG) ARG
    #include "mystuff.h"
}

principal.cpp:

#include "enum.h"
#define ENUM_ELEMENT(ARG) void do_##ARG();
#include "mystuff.h"

element value = getValue();
switch(value) {
    #define ENUM_ELEMENT(ARG) case ARG: do_##ARG(); break;
    #include "mystuff.h"
    default: std::terminate();
}

Donc, pour ajouter un nouvel élément "qux", vous l'ajouter à mes trucs.h et écrire la fonction do_qux . Vous n'avez pas à toucher le code de répartition.

bien sûr si les valeurs de votre enum doivent être des entiers non consécutifs spécifiques, alors vous finissez par maintenir la définition de l'enum et le ENUM_ELEMENT(foo) ... liste séparément, ce qui est pénible.

2
répondu Steve Jessop 2009-09-07 20:09:23

Non

cependant, vous pouvez définir votre propre classe qui implémente des fonctionnalités de type enum avec des itérations. Vous vous souviendrez peut-être d'un truc du Pré 1.5 Java days, appelé "type safe enum design pattern". Vous pourriez faire le C++ équivalent.

1
répondu DigitalRoss 2009-09-07 19:47:23

cela me semble hacky, mais peut convenir à vos fins:

enum Blah {
  FOO,
  BAR,
  NUM_BLAHS
};

// later on
for (int i = 0; i < NUM_BLAHS; ++i) {
  switch (i) {
  case FOO:
    // foo stuff
    break;
  case BAR:
    // bar stuff
    break;
  default:
    // you're missing a case statement
  }
}

si vous avez besoin d'une valeur de départ spéciale, vous pouvez en faire une constante et la mettre dans votre enum. Je n'ai pas vérifier si cette compile, mais il devrait être proche d'être là :-). Espérons que cette aide.

je pense que cette approche pourrait être un bon équilibre pour votre cas d'utilisation. L'utiliser si vous n'avez pas besoin de faire cela pour un tas de différents types énumérés et vous ne voulez pas avoir affaire avec préprocesseur choses. Assurez-vous juste de commenter et probablement ajouter un TODO pour le changer à une date ultérieure à quelque chose de mieux: -).

1
répondu Tom 2009-09-07 20:00:36

je fais souvent ça comme ça:

enum abc
{    
    abc_begin,
    a = abc_begin,
    b,
    c,
    abc_end
};

void foo()
{
    for( auto&& r : range(abc_begin,abc_end) )
    {
        cout << r;
    }
}



range est complètement générique, et défini comme suit:

template <typename T>
class Range
{
public:
    Range( const T& beg, const T& end ) : b(beg), e(end) {}
    struct iterator
    {
        T val;
        T operator*() { return val; }
        iterator& operator++() { val = (T)( 1+val ); return *this; }
        bool operator!=(const iterator& i2) { return val != i2.val; }
    };
    iterator begin() const { return{b}; }
    iterator end() const { return{e}; }
private:
    const T& b;
    const T& e;
};

template <typename T>
Range<T> range( const T& beg, const T& end ) { return Range<T>(beg,end); }
1
répondu sp2danny 2014-10-25 07:03:14

vous pouvez effectuer certaines des techniques d'exécution proposées statiquement avec TMP.

#include <iostream>

enum abc
{
    a,
    b,
    c,
    end
};

void function_call(abc val)
{
    std::cout << val << std::endl;
}

template<abc val>
struct iterator_t
{
    static void run()
    {
        function_call(val);

        iterator_t<static_cast<abc>(val + 1)>::run();
    }
};

template<>
struct iterator_t<end>
{
    static void run()
    {
    }
};

int main()
{
    iterator_t<a>::run();

    return 0;
}

le résultat de ce programme est:

0
1
2

Voir Ch 1 de Abrahams, Gurtovoy "C++ Template Métaprogrammation" pour un bon traitement de cette technique. L'avantage de le faire de cette façon par rapport aux techniques d'exécution proposées est que, lorsque vous optimisez ce code, il peut s'aligner sur la statique et est à peu près équivalent à:

function_call(a);
function_call(b);
function_call(c);

function_call Inline pour encore plus d'aide de la part du compilateur.

les mêmes critiques des autres techniques d'itération d'énumération s'appliquent ici. Cette technique ne fonctionne que si votre énumération augmente continuellement de A à fin par un.

0
répondu Ken Smith 2010-08-02 23:58:09

modèle D'amour mais je vais prendre note de cela pour mon futur / l'usage d'autres personnes donc nous ne sommes pas perdus avec l'un des ci-dessus.

Enums sont commodes pour le plaisir de comparer des choses dans un ordre connu. Ils sont généralement utilisés codés en dur dans des fonctions pour la lisibilité contre des valeurs entières. Un peu semblables aux définitions du préprocesseur, à l'exception qu'elles ne sont pas remplacées par des littérales, mais conservées et accessibles à l'exécution.

si nous avions un enum définissant les codes d'erreur html et que nous savions que les codes d'erreur dans les 500s sont des erreurs de serveur, il pourrait être plus agréable de lire quelque chose comme:

enum HtmlCodes {CONTINUE_CODE=100,CLIENT_ERROR=400,SERVER_ERROR=500,NON_STANDARD=600};

if(errorCode >= SERVER_ERROR && errorCode < NON_STANDARD)

que

if(errorCode >= 500 && errorCode < 600)

la partie clé est celle-ci, ils sont similaires aux tableaux! Mais de cast valeurs entières .

court exemple:

enum Suit {Diamonds, Hearts, Clubs, Spades};
//does something with values in the enum past "Hearts" in this case
for(int i=0;i<4;i++){
   //Could also use i or Hearts, because the enum will turns these both back into an int 
   if( (Suit)(i) > 1 )
   {
      //Whatever we'd like to do with (Suit)(i)
   }
}

Souvent, les enums sont aussi utilisés avec des char* matrices ou des tableaux de chaîne, de sorte que vous pouvez imprimer un message avec la valeur associée. Normalement ce sont juste des tableaux avec le même ensemble de valeurs dans l'enum, comme ceci:

char* Suits[4] = {"Diamonds", "Hearts", "Clubs", "Spades"};
//Getting a little redundant
cout << Suits[Clubs] << endl;
//We might want to add this to the above
//cout << Suits[(Suit)(i)] << endl;

et bien sûr, il est encore plus agréable de créer une classe générique qui gère les itérations pour les énums comme les réponses ci-dessus.

0
répondu TheUnknownGeek 2014-11-12 05:34:43