9

How awful is it - or is it perfectly acceptable - to index a loop on an enumeration?

I have an enumeration defined. The values of the literals are default values. The assigned values do not have any significance, will not have any significance, and the values of any literals added in the future will also not have any significance. It's just defined to limit the allowed values and to make things easier to follow. Therefore the values will always start at 0 and increase by 1.

Can I set up a loop like so:

enum MyEnum
{
    value1,
    value2,
    value3,
    maxValue
}

for(MyEnum i = value1; i < maxValue; i = static_cast<MyEnum>(i+1)){}
1
  • 1
    Stop! You're making me nostalgic for Pascal. Commented Mar 19, 2010 at 23:49

9 Answers 9

9

I wrote an enum iterator a while ago for these cases

enum Foo {
 A, B, C, Last
};

typedef litb::enum_iterator<Foo, A, Last> FooIterator;

int main() {
  FooIterator b(A), e;
  std::cout << std::distance(b, e)  << " values:" << std::endl;
  std::copy(b, e, std::ostream_iterator<Foo>(std::cout, "\n"));

  while(b != e) doIt(*b++);
}

If you are interested, here is the code. If you like, you can extend it to be a random access iterator by providing +, <, [] and friends. Algorithms like std::distance will thank you by providing O(1) time complexity for the then random-access iterator.

#include <cassert>

namespace litb {

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

  enum_iterator &operator=(Enum c) {
    this->assign(c);
    return *this;
  }

  enum_iterator &operator++() {
    this->inc();
    return *this;
  }

  enum_iterator operator++(int) {
    enum_iterator cpy(*this);
    this->inc();
    return cpy;
  }

  enum_iterator &operator--() {
    this->dec();
    return *this;
  }

  enum_iterator operator--(int) {
    enum_iterator cpy(*this);
    this->dec();
    return cpy;
  }

  Enum operator*() const {
    assert(c != End && "not dereferencable!");
    return c;
  }

  bool equals(enum_iterator other) const {
    return other.c == c;
  }

private:
  void assign(Enum c) {
    assert(c >= Begin && c <= End);
    this->c = c; 
  }

  void inc() {
    assert(c != End && "incrementing past end");
    c = static_cast<Enum>(c + 1);
  }

  void dec() {
    assert(c != Begin && "decrementing beyond begin");
    c = static_cast<Enum>(c - 1);
  }

private:
  Enum c;
};

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

template<typename Enum, Enum Begin, Enum End>
bool operator!=(enum_iterator<Enum, Begin, End> e1, enum_iterator<Enum, Begin, End> e2) {
  return !(e1 == e2);
}

} // litb
Sign up to request clarification or add additional context in comments.

4 Comments

That is slick. :) Just gotta throw the comma operator in there somewhere for your signature.
Why operator== but no operator> (and friends)?
@Chris i didn't need it back then since my goal was iteration of the range, so i went to implement the bidirectional-iterator interface. But a op> would certainly make sense for this enum iterator.
@Chris, also the actual enum iterator supports sparse ranges so it made less sense to support random access to me back then. I changed the iterator class for this answer to only support sequential enums. See stackoverflow.com/questions/300592/enum-in-c-like-enum-in-ada/… (code for the actual enum iterator and ENUM macro is located here: johannes-schaub.de/enums.tar.gz )
4

As far as I'm concerned, that's just fine. I'm sure some purist out there somewhere will freak out, but as far as the language spec is concerned, that code will work correctly, so you should feel free to go for it if it makes your life easier.

Comments

2

It might be helpful to do this.

enum MyEnum
{
    first,
    value1,
    value2,
    value3,
    last
}

1 Comment

That gives "first" its own unique value. Normal loop idioms are half-open - inclusive at the start and exclusive at the end. I suggest using "first = value1" at the end of the enum, or simply renaming "value1" in a way that makes it clear it is and always will be the first one.
0

That is fine as long as you don't specifically set the value of an enum value. Your example is fine, but this could be bad:

// this would be bad
enum MyEnum
{
    value1,
    value2 = 2,
    value3,
    maxValue
};

As the standard states in 7.2/1:

An enumerator-definition without an initializer gives the enumerator the value obtained by increasing the value of the previous enumerator by one.

2 Comments

That's why I specified that the values of the literals are not significant and will never be. They get the default values.
@Rachel - I was just trying to be clear about what could go wrong. As I said i the first sentence of my answer, it's perfectly fine the way you do it.
0

I don't know any specific use of this that is actually useful and I think is bad code. What if one put things like this as seen in many libraries ?

enum MyEnum
{
    value1,
    value2,
    value3,
    maxValue = 99999;
};

1 Comment

IMO, thats a good reason to add a comment to the enum - tell them not to.
0

It's not easy...

The only solution I ever found was to actually implement a class over the enum, and then use a macro to define the enum...

DEFINE_NEW_ENUM(MyEnum, (value1)(value2)(value3))

The basic idea is to store the values in a STL container, stored statically in a template class depending on the MyEnum symbol. This way you get a perfectly well defined iteration, and you even get an invalid state for naught (because of the end()).

I used a sorted vector of pair<Enum,std::string> so that I could benefit from pretty printing in the logs and long lasting serialization (and because it's faster than an associative container for little sets of data and takes less memory).

I didn't found a better way yet, does C++11 finally get iteration on enums or not ?

Comments

0

Therefore the values will always start at 0 and increase by 1.

Keep in mind that unless this is written into the coding standard where you work, when a maintenance programmer comes along in 6 months and makes a change to an enumeration like:

enum MyEnum 
{ 
    value1, 
    value2 = 5, 
    value3, 
    maxValue 
}

because of a new requirement, you'll get to explain why their legitimate change broke the application.

To attach values to names you could use a map:

typedef std::map<std::string,int> ValueMap;
ValueMap myMap;
myMap.insert(make_pair("value1", 0));
myMap.insert(make_pair("value2", 1));
myMap.insert(make_pair("value3", 2));

for( ValueMap::iterator iter = theMap.begin(); iter != theMap.end(); ++iter )
{
  func(iter->second);
}

If the names don't matter you could use a C-style array:

int values[] = { 0, 1, 2 };
int valuesSize = sizeof(values) / sizeof(values[0]);

for (int i = 0; i < valuesSize; ++i)
{
  func(values[i]);
}

Or a comparable method using a std::vector.

Also, if what you say is true, and the values will always start at 0 and increase by 1 then I'm confused as to why this wouldn't work:

const int categoryStartValue = 0;
const int categoryEndValue = 4;

for (int i = categoryStartValue; i < categoryEndValue; ++i)
{
  func(i);
}

Comments

0

I usually define begin and end values in my enums when I need iteration, with a special syntax like the following (with doxygen style comments to clarify):

enum FooType {
    FT__BEGIN = 0,        ///< iteration sentinel
    FT_BAR = FT__BEGIN,   ///< yarr!
    FT_BAZ,               ///< later, when the buzz hits  
    FT_BEEP,              ///< makes things go beep
    FT_BOOP,              ///< makes things go boop
    FT__END              ///< iteration sentinel
}

FT_ is there to help with namespacing (to avoid clashes between enums) and the double underscores help distinguish between real values and iteration sentinels.

Sidenote:

Writing this I start worry about the double underscore not being allowed for user identifier (it's implementation reserved, IIRC?), but I'm yet to hit a problem (after 5-6 years of coding in this style). It might be ok since I always put all my types in a namespace to avoid polluting and interfering with the global namespace.

Comments

-1

You could wrap the loop body with a switch statement to protect against non-incremental values. It's potentially super slow, as in the maxValue = 9999; case, but it might not be the worst thing ever. I see this style here all the time in our code bases

enum MyEnum  {
 value1,  
 value2,  
 value3,  
 maxValue  
}  
for(MyEnum i = value1; i < maxValue; i = static_cast<MyEnum>(i+1)){
     switch(i)
     {
       case value1:
       case value2:
       case value3:
                    //actual code
     }

}  

1 Comment

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.