Bertwim van Beest
Bertwim van Beest

Reputation: 1811

Iterating over an enum class: it works, but is it conforming the standard?

In the c++ code below+output, the following is used to iterate over the values of an enum class, say MyEnumClass: (the enum class has contiguous values)

for (const auto& e : MyEnumClass() )
{
  // ...
} 

In order for the range-based for-loop to work, I had to define the operators ++ and *, as well as begin(MyEnumClass) and end(MyEnumClass).

Although this works, I have not been able to find if it is a legitimate approach. Is it according to the C++ standard? The operators and begin/end I understand, but what is really the meaning here of MyEnumClass()?

I hope there is somebody who can shed some light on this.

Regards, Bertwim

#include <iostream>

enum class MyEnumClass : int
{
   BEGIN,

   ONE=BEGIN, TWO, THREE, FOUR, // consecutive values!

   END
};

auto begin( MyEnumClass )
{
   return MyEnumClass::BEGIN;
}

auto end( MyEnumClass )
{
   return MyEnumClass::END;
}

MyEnumClass operator++( MyEnumClass& p )
{
   return p = static_cast<MyEnumClass>( static_cast<unsigned>( p ) + 1 );
}

MyEnumClass operator*( MyEnumClass p )
{
   return p;
}


int main( int, char*[] )
{
   for (const auto& e : MyEnumClass() )
   {
      std::cout << static_cast<int>( e ) << std::endl;
   }
}

Compilation and run: (compiler is gcc 11.3.0)
[519] cpptest> g++ main.cpp && ./a.out
0
1
2
3

Upvotes: 9

Views: 2816

Answers (1)

user17732522
user17732522

Reputation: 76794

It is perfectly fine. You only need to make sure that the additional functions you defined are placed in exactly the same namespace as the enumeration, so that they can be found via argument-dependent lookup.

You also made a minor mistake by using unsigned instead of int in one of the casts. This mistake can be avoided by replacing the static_cast's with C++23's std::to_underlying, which can also easily be implemented before C++23.

Also, using const auto& instead of just auto in the range-for loop is a bit weird. operator* is returning by-value, so the reference will anyway be bound to a temporary, making the reference part potentially misleading.

MyEnumClass() in the range-expression of the range-for loop is simply a MyEnumClass prvalue which is value-initialized, meaning here zero-initialized, meaning here initialized to static_cast<MyEnumClass>(0), meaning here initialized to MyEnumClass::BEGIN. So instead of MyEnumClass() you could have written MyEnumClass::BEGIN, which actually reads clearer.

The problem with this approach is that the type MyEnumClass now represents a single enumeration value as well as a range of enumeration values. That can get confusing quickly. It would be preferable to write a class with proper iterators which specifically represents the enumeration range.

Upvotes: 4

Related Questions