Loop on enumeration values
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 w开发者_如何转开发ill 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)){}
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
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.
It might be helpful to do this.
enum MyEnum
{
first,
value1,
value2,
value3,
last
}
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.
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;
};
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 ?
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);
}
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.
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
}
}
精彩评论