Enumerate over an enum in C++

W C++, czy możliwe jest wyliczenie przez enum (runtime lub compile time (preferowane)) i wywołanie funkcji/wygenerowanie kodu dla każdej iteracji?

Przykładowy przypadek użycia:

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

Wiarygodne duplikaty:

Author: Community, 2009-09-07

9 answers

Aby dodać do@StackedCrooked odpowiedz, możesz przeciążyć operator++, operator-- i operator* i mają funkcjonalność podobną do iteratora.

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

Przetestujmy z jakimś <algorithm> szablonem

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

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

Jest stałym dwukierunkowym iteratorem. Oto Klasa wielokrotnego użytku, którą zakodowałem podczas robienia tego ręcznie powyżej. Zauważyłem, że może działać dla wielu innych enum, więc powtarzanie tego samego kodu od nowa jest dość żmudne.]}

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

Implementacja następuje.

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
Author: Johannes Schaub - litb,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-09-08 02:17:04

C++ obecnie nie udostępnia iteracji enumeratora. Mimo to czasami pojawia się taka potrzeba. Powszechnym obejściem jest dodawanie wartości oznaczających początek i koniec. Na przykład:

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
Author: StackedCrooked,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-09-09 02:29:45

Nie jest to możliwe bez odrobiny pracy fizycznej. Wiele pracy można wykonać za pomocą makr, jeśli chcesz zagłębić się w ten obszar.

 4
Author: Konrad Rudolph,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-09-07 19:36:06

Rozszerzając to, co mówi Konrad, jednym z możliwych idiomów w przypadku "Generuj kod dla każdej iteracji" jest użycie dołączonego pliku do reprezentowania wyliczenia:

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

Aby dodać nowy element "qux", należy dodać go do mystuff.h i zapisz funkcję do_qux. Nie musisz dotykać kodu.

Oczywiście, jeśli wartości w Twoim enum muszą być konkretnymi liczbami całkowitymi, wtedy kończysz zachowanie definicji enum i ENUM_ELEMENT(foo)... lista osobno, co jest niechlujne.

 2
Author: Steve Jessop,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-09-07 20:09:23

Nie

Możesz jednak zdefiniować własną klasę, która implementuje funkcje podobne do enum za pomocą iteracji. Możesz przypomnieć sobie sztuczkę z przed 1.5 dni Java, o nazwie "type safe enum design pattern". Możesz zrobić odpowiednik C++.

 1
Author: DigitalRoss,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-09-07 19:47:23

To wydaje mi się trudne, ale może pasować do Twoich celów:

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

Jeśli potrzebujesz specjalnej wartości początkowej, możesz ustawić ją jako stałą i ustawić w swoim enum. Nie sprawdzałem czy to kompiluje, ale powinno być blisko: -). Mam nadzieję, że to pomoże.

Myślę, że takie podejście może być dobrą równowagą dla Twojego przypadku użycia. Użyj go, jeśli nie musisz tego robić dla kilku różnych typów wyliczeń i nie chcesz zajmować się sprawami preprocesora. Tylko upewnij się, że komentujesz i prawdopodobnie dodaj TODO, aby zmienić go w późniejszym terminie na coś lepszego :-).

 1
Author: Tom,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-09-07 20:00:36

Zwykle robię to tak:

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

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


range jest całkowicie ogólny i zdefiniowany w następujący sposób:

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
Author: sp2danny,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-10-25 07:03:14

Można wykonywać niektóre z proponowanych technik runtime statycznie z 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;
}

Wyjście z tego programu to:

0
1
2
Zobacz Ch 1 z Abrahams, Gurtovoy "C++ Template Metaprogramming" dla dobrego traktowania tej techniki. Zaletą robienia tego w ten sposób w porównaniu z proponowanymi technikami uruchomieniowymi jest to, że po zoptymalizowaniu tego kodu może on wbudować statykę i jest mniej więcej równoważny:
function_call(a);
function_call(b);
function_call(c);

Inline function_call dla jeszcze większej pomocy z kompilator.

Ta sama krytyka innych technik iteracji wyliczeń stosuje się tutaj. Ta technika działa tylko wtedy, gdy twoje wyliczenie wzrasta w sposób ciągły od A do końca przez jedynki.

 0
Author: Ken Smith,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-08-02 23:58:09

Uwielbiam szablony, ale zamierzam zanotować to dla mojej przyszłości/wykorzystania innych ludzi, więc nie zgubimy się z żadnym z powyższych.

Wyliczenia są wygodne do porównywania rzeczy w znany uporządkowany sposób. Są one zwykle używane do kodowania na twardo w funkcjach w celu odczytania wartości całkowitych. Nieco podobne do definicji preprocesora, z tym wyjątkiem, że nie są one zastępowane literałami, ale przechowywane i dostępne w trybie runtime.

Gdybyśmy mieli enum definiowanie kodów błędów html i wiedzieliśmy, że kody błędów w 500s są błędami serwera, może być ładniej przeczytać coś w stylu:

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

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

Niż

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

Kluczową częścią jest to, że są one podobne do tablic! ale są używane do gipsowe wartości całkowite .

Krótki przykład:

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

Często enums są również używane z tablicami char * lub tablicami łańcuchowymi, dzięki czemu można wydrukować wiadomość z powiązaną wartością. Normalnie są po prostu tablice z tym samym zestawem wartości w enum, jak tak:

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;

I oczywiście jeszcze przyjemniej jest stworzyć klasę generyczną, która obsługuje iterację dla enum, jak w powyższych odpowiedziach.

 0
Author: TheUnknownGeek,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-11-12 05:34:43