web-dev-qa-db-fra.com

Énumérer sur une énumération en C++

En C++, est-il possible d'énumérer sur une énumération (soit au moment de l'exécution ou de la compilation (préféré)) et d'appeler des fonctions/de 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__);    
}

Doublés plausibles:

38
jameszhao00

Pour ajouter à @StackedCrooked answer, vous pouvez surcharger operator++, operator-- et operator* et avoir une fonctionnalité semblable à celle d'un 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;
}

Testons avec un modèle <algorithm>

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 que j'ai codée en la faisant manuellement ci-dessus. J'ai remarqué que cela pourrait fonctionner pour beaucoup d'autres enums, il est donc fastidieux de répéter le même code

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

C++ ne fournit actuellement pas d'itération d'énumérateur. Malgré cela, le besoin s'en fait parfois sentir. Une solution de contournement 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
StackedCrooked

Ni est possible sans un peu de travail manuel. Une grande partie du travail peut être effectuée à l’aide de macros, si vous souhaitez explorer ce domaine.

4
Konrad Rudolph

En développant ce que dit Konrad, un exemple possible dans le cas de "générer du code pour chaque itération" consiste à utiliser un fichier inclus pour représenter l'énumération:

mystuff.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"
}

main.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();
}

Ainsi, pour ajouter un nouvel élément "qux", vous l'ajoutez à mystuff.h et écrivez la fonction do_qux. Vous n'êtes pas obligé de toucher le code d'envoi.

Bien sûr, si les valeurs de votre enum doivent être des entiers spécifiques non consécutifs, vous finissez par conserver la définition de enum et la liste ENUM_ELEMENT(foo)... séparément, ce qui est désordonné.

2
Steve Jessop

Je fais habituellement ç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 se définit 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
sp2danny

Non

Cependant, vous pouvez définir votre propre classe qui implémente des fonctionnalités de type énumération avec des itérations. Vous vous rappelez peut-être une astuce des jours Java antérieurs à la 1.5, appelée "modèle de conception de type enum". Vous pouvez faire l'équivalent C++.

1
DigitalRoss

Cela me semble hacky, mais peut convenir à vos objectifs:

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 définir dans votre enum. Je n'ai pas vérifié si cela compile, mais ça devrait être proche d'être là :-). J'espère que cela t'aides.

Je pense que cette approche pourrait constituer un bon équilibre pour votre cas d'utilisation. Utilisez-le si vous n'avez pas besoin de le faire pour un tas de types énumérés différents et que vous ne voulez pas vous occuper de choses de pré-traitement. Assurez-vous simplement de commenter et probablement d’ajouter un TODO pour le changer ultérieurement en quelque chose de mieux :-).

1
Tom

Vous pouvez effectuer certaines des techniques d'exécution proposées de manière statique 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;
}

La sortie de ce programme est:

0
1
2

Voir le chapitre 1 d'Abrahams, Gurtovoy "Métaprogrammation de modèles C++" 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 intégrer la statique et équivaut à peu près à:

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

Function_call en ligne pour encore plus d'aide du compilateur.

Les mêmes critiques d'autres techniques d'itération de dénombrement s'appliquent ici. Cette technique ne fonctionne que si votre énumération incrémente continuellement d'une extrémité à l'autre.

0
Ken Smith

J'adore créer des modèles, mais je vais en prendre note pour mon utilisation future/par d'autres personnes, de sorte que nous ne perdons aucun de ces éléments.

Les énumérations sont pratiques pour comparer les choses de manière ordonnée connue. Ils sont généralement utilisés dans des fonctions codées en dur dans un souci de lisibilité par rapport aux valeurs entières. Un peu similaire aux définitions du préprocesseur, à la différence qu'elles ne sont pas remplacées par des littéraux, mais conservées et accessibles au moment de l'exécution.

Si nous avions une énumération définissant les codes d’erreur HTML et que nous savions que les codes d’erreur du 500 étaient des erreurs de serveur, il serait peut-ê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 la suivante: ils ressemblent aux tableaux! Mais sont utilisés pour cast valeurs entières

Petit 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, des énumérations sont également utilisées avec des tableaux char * ou des tableaux de chaînes afin que vous puissiez imprimer un message avec la valeur associée. Normalement, ce ne sont que des tableaux avec le même ensemble de valeurs dans l'énumération, 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 l'itération pour des énumérations telles que celles décrites ci-dessus.

0
TheUnknownGeek