tags:

views:

460

answers:

10

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  A: 

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.

Carl Norum
A: 

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.

R Samuel Klatchko
That's why I specified that the values of the literals are not significant and will never be. They get the default values.
Rachel
@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.
R Samuel Klatchko
A: 

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;
};
fabrizioM
Then they'd be maliciously breaking my code.
Rachel
IMO, thats a good reason to add a comment to the enum - tell them not to.
Steve314
A: 

There's nothing wrong with the code itself, but a (const) vector<> would be a more robust way of doing the same thing. If you have "gaps" in your enum declarations (like R Samuel Klatchko demonstrated), a (switch/case) lookup table would be the best way to go.

Again, both of the above assume that you're trying to traverse the enum in some way or another. If you're trying to loop enum.minValue to enum.maxValue amount of times, I'm also confused as to why you would do this.

David Titarenco
Yes, trying to traverse the enum. We chose to have an enum and not a list of numbers because it makes the code easier to follow when we're dealing with significant, explanatory names in this application.
Rachel
+2  A: 

It might be helpful to do this.

enum MyEnum
{
    first,
    value1,
    value2,
    value3,
    last
}
thereisnospork
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.
Steve314
A: 

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);
}
Bill
A: 

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
     }

}  
Daniel
Ahhh! The dreaded For / Case pattern! http://thedailywtf.com/Articles/The_FOR-CASE_paradigm.aspx http://thedailywtf.com/Articles/Switched_on_Loops.aspx
Chris Lutz
A: 

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 ?

Matthieu M.
+5  A: 

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
Johannes Schaub - litb
That is slick. :) Just gotta throw the comma operator in there somewhere for your signature.
GMan
Why `operator==` but no `operator>` (and friends)?
Chris Lutz
@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.
Johannes Schaub - litb
@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 http://stackoverflow.com/questions/300592/enum-in-c-like-enum-in-ada/1436615#1436615 (code for the actual enum iterator and `ENUM` macro is located here: http://www.johannes-schaub.de/enums.tar.gz )
Johannes Schaub - litb
A: 

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.

Marcus Lindblom