Enumerate over an enum in C++

26,344

Solution 1

To add to @StackedCrooked answer, you can overload operator++, operator-- and operator* and have iterator like functionality.

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

Let's test with some <algorithm> template

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

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

Now, Color is a constant bidirectional iterator. Here is a reusable class i coded while doing it manually above. I noticed it could work for many more enums, so repeating the same code all over again is quite tedious

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

Implementation follows.

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

Solution 2

C++ currently does not provide enumerator iteration. Despite that, the need sometimes arises for this. A common workaround is to add values that mark the beginning and the ending. For example:

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

Solution 3

Neither is possible without a little manual labour. A lot of the work can be done by macros, if you’re willing to delve into that area.

Solution 4

Expanding on what Konrad says, one possible idiom in the case of "generate code for each iteration" is to use an included file to represent the enumeration:

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

So, to add a new element "qux", you add it to mystuff.h and write the do_qux function. You don't have to touch the dispatch code.

Of course if the values in your enum need to be specific non-consecutive integers, then you end up maintaining the enum definition and the ENUM_ELEMENT(foo)... list separately, which is messy.

Solution 5

This seems hacky to me, but may suit your purposes:

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

If you need a special start value, you can make that a constant and set it in your enum. I didn't check if this compiles, but it should be close to being there :-). Hope this helps.

I think this approach might be a good balance for your use case. Use it if you don't need to do this for a bunch of different enumerated types and you don't want to deal with preprocessor stuff. Just make sure you comment and probably add a TODO to change it at a later date to something better :-).

Share:
26,344
jameszhao00
Author by

jameszhao00

rabble

Updated on September 09, 2020

Comments

  • jameszhao00
    jameszhao00 over 3 years

    In C++, Is it possible to enumerate over an enum (either runtime or compile time (preferred)) and call functions/generate code for each iteration?

    Sample use case:

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

    Plausible duplicates:

  • Konrad Rudolph
    Konrad Rudolph over 14 years
    The for and nested switch are utterly useless/meaningless since you use each of the consecutive values anyway. Just omit both and execute foo stuff, followed by bar stuff etc. directly.
  • jmucchiello
    jmucchiello over 14 years
    This is only useful in a test program where you run a series of tests. The start/end conditions would be variables so if you want to only run one test you set start = end: for (int i = start; i <= end; ++i) { .... } In all other cases, I agree with Konrad: switch inside a for is probably a bad design.
  • SimonHawkins
    SimonHawkins over 14 years
    A for loop to hit each case of a switch statement? Please. This is Stackoverflow, not thedailywtf.com. That's just a needlessly complicated way to do "foo stuff", then "bar stuff". That doesn't require any sort of flow control.
  • Tom
    Tom over 14 years
    @Konrad and Alan: I think the point of this would be to make sure you hit every enum. If you crash or output a warning or something in the default statement, you at least recognize at runtime that you forgot to include a new case after adding a new enum. I really wasn't sure what the application was of the OP, so I just put down what I thought was the simplest way to do what was asked. It seems the original question was updated and this doesn't make sense anymore and I misunderstood the original question. However...this is slightly better than just calling each method separately.