允许对枚举类使用基于范围的 For?

我有一个循环的代码块,其中我循环遍历 enum class的所有成员。

与新的 range-based for相比,我目前使用的 for循环看起来非常笨拙。

有没有什么方法可以利用新的 C + + 11特性来减少我当前 for循环的冗长?

我想改进的现行守则:

enum class COLOR
{
Blue,
Red,
Green,
Purple,
First=Blue,
Last=Purple
};


inline COLOR operator++( COLOR& x ) { return x = (COLOR)(((int)(x) + 1)); }


int main(int argc, char** argv)
{
// any way to improve the next line with range-based for?
for( COLOR c=COLOR::First; c!=COLOR::Last; ++c )
{
// do work
}
return 0;
}

换句话说,如果我能做这样的事情就好了:

for( const auto& c : COLOR )
{
// do work
}
70530 次浏览

Iterating enumerations with the enumeration itself as an iterator is a poor idea, and I recommend using an actual iterator as in deft_code's answer. But if this is really what you want:

COLOR operator++(COLOR& x) {
return x = (COLOR)(std::underlying_type<COLOR>::type(x) + 1);
}


COLOR operator*(COLOR c) {
return c;
}


COLOR begin(COLOR r) {
return COLOR::First;
}


COLOR end(COLOR r) {
COLOR l=COLOR::Last;
return ++l;
}


int main() {
//note the parenthesis after COLOR to make an instance
for(const auto& c : COLOR()) {
//do work
}
return 0;
}

Working here: http://ideone.com/cyTGD8


On the iterator side of things, the easiest way is simply:
const COLOR COLORS[] = {COLOR::Blue, COLOR::Red, COLOR::Green, COLOR::Purple};
const COLOR (&COLORREF)[(int)COLOR::Last+1] = COLORS;


int main() {
for(const auto& c : COLORS) {
//do work
}
return 0;
}

As seen here: http://coliru.stacked-crooked.com/a/5d356cc91556d6ef

(The separate defintinion and the reference of the array makes it a compiler error if the number of colors doesn't match the number of elements in the array. Excellent easy safety check.)

Here's a tested example (GCC 4.6.1):

enum class COLOR
{
Blue,
Red,
Green,
Purple,
First=Blue,
Last=Purple
};


COLOR operator++( COLOR& x ) { return x = (COLOR)(((int)(x) + 1)); }


COLOR operator*(COLOR c) {return c;}


COLOR begin(COLOR r) {return COLOR::First;}
// end iterator needs to return one past the end!
COLOR end(COLOR r)   {return COLOR(int(COLOR::Last) + 1);}




int main()
{
for (const auto& color : COLOR()) std::cout << int(color); //0123
return 0;
}

I personally don't like overloading the ++ operator for enums. Often incrementing an enum value doesn't really make sense. All that is really wanted is a way to iterator over the enum.

Below is an generic Enum class that supports iteration. It's functional but incomplete. A real implementation would do well to restrict access to the constructor and add all the iterator traits.

#include <iostream>


template< typename T >
class Enum
{
public:
class Iterator
{
public:
Iterator( int value ) :
m_value( value )
{ }


T operator*( void ) const
{
return (T)m_value;
}


void operator++( void )
{
++m_value;
}


bool operator!=( Iterator rhs )
{
return m_value != rhs.m_value;
}


private:
int m_value;
};


};


template< typename T >
typename Enum<T>::Iterator begin( Enum<T> )
{
return typename Enum<T>::Iterator( (int)T::First );
}


template< typename T >
typename Enum<T>::Iterator end( Enum<T> )
{
return typename Enum<T>::Iterator( ((int)T::Last) + 1 );
}


enum class Color
{
Red,
Green,
Blue,
First = Red,
Last = Blue
};


int main()
{
for( auto e: Enum<Color>() )
{
std::cout << ((int)e) << std::endl;
}
}

You could probably do something clever with boost::mpl, a rough version might look like:

#include <typeinfo>


// ---------------------------------------------------------------------------|
// Boost MPL
// ---------------------------------------------------------------------------|
#include <boost/mpl/for_each.hpp>
#include <boost/mpl/iterator_range.hpp>
#include <boost/mpl/range_c.hpp>


namespace mpl = boost::mpl;


using namespace std;


enum class COLOR
{
Blue,
Red,
Green,
Purple,
Last
};


struct enumValPrinter
{
template< typename T >
void operator() (const T&)
{
cout << "enumValPrinter with: " << typeid( T ).name() << " : "
<< T::value << "\n";
}
};


int main(int, char**)
{
typedef mpl::range_c< int, static_cast<int>( COLOR::Blue ),
static_cast<int>( COLOR::Last ) > Colors;
mpl::for_each< Colors >( enumValPrinter() );
return 0;
}

I like the idea a lot and have often wished for it.

The problem I see is what happens when there is a repeated numeric value for an enum item. All the implementations I see above require casts to integral type and ++. Ultimately, I think language support might be required to truly iterate over each item in all cases. It would remove the need to have First, Last or Begin, End although I don't object to this too much. It's like looking for begin() end() for containers.

enum class COLOR
{
Blue,
Red,
Green,
Mauve = 0,
Purple,
Last
};

The numbering starts over at Mauve.

enum class Color {
blue,
red,
green = 5,
purple
};
const std::array<Color,4> all_colors = {Color::blue, Color::red, Color::green, Color::purple};

Then:

for (Color c : all_colors) {
//...
}

Many times I use it like this, where I want a 'none' value:

// Color of a piece on a chess board
enum class Color {
white,
black,
none
};
const std::array<Color,3> colors = {Color::white, Color::black};


template <typename CONTAINER>
bool has_item (CONTAINER const & c, typename CONTAINER::const_reference v) {
return std::find(c.begin(), c.end(), v) != c.end();
}


bool is_valid (Color c) {
return has_item(colors, c) || c == Color::none;
}


bool do_it (Color c) {
assert(has_item(colors, c)); // here I want a real color, not none
// ...
}


bool stop_it (Color c) {
assert(is_valid(c));         // but here I just want something valid
// ...
}

If you're a terrible person you can get this behavior with the preprocessor, something like:

#include <vector>
#include <cstdio>


#define ENUM_NAME COLOR
#define ENUM_VALUES \
ENUM_VALUE(Blue) \
ENUM_VALUE(Red) \
ENUM_VALUE(Green) \
ENUM_VALUE(Purple)


// This block would be a #include "make_iterable_enum.h"
#define ENUM_VALUE(v) v,
enum class ENUM_NAME {ENUM_VALUES};
#undef ENUM_VALUE
#define ENUM_VALUE(v) ENUM_NAME::v,
#define VECTOR_NAME(v) values_ ## v
#define EXPAND_TO_VECTOR_NAME(v) VECTOR_NAME(v)
const std::vector<ENUM_NAME> EXPAND_TO_VECTOR_NAME(ENUM_NAME){ENUM_VALUES};
#undef ENUM_VALUE
#undef ENUM_NAME
#undef ENUM_VALUES
#undef VECTOR_NAME
#undef EXPAND_TO_VECTOR_NAME
// end #included block


int main() {
for (auto v : COLOR_values) {
printf("%d\n", (int)v);
}
}

With minor modifications this could also support eg. ENUM_SETVALUE(Blue, 4) and making a const map from eg. COLOR::Blue to "Blue". And vice-versa.

I wish the standard had just built these features in as options to enum class. None of the workarounds are good.

I'm sure that you can iterate over the members of a C++ initializer_list, so I reckon I've done this in the past:

enum class Color {Red, Green, Blue};


for (const Color c : {Color::Red, Color::Green, Color::Blue})
{
}

Whether there are issues with this, I don't know, but I thought I'd suggest it as it is concise, but not ideal if there are a lot of Colors.

Whether or not you approve of incrementing enums there are times when it is useful. So here's a simple way of doing so:

enum class COLOR
{
Blue,
Red,
Green,
Purple,
First=Blue,
Last=Purple
};


COLOR c;


++( *reinterpret_cast<int*>( &c));

There is no overhead since the compiler will take care of the casting and de-referencing. Add range checking or other capabilities as necessary.

As a modification of @deft_code's answer, you don't need to define the First and the Last in your enum class, just add two parameters for the templated Enum class.

template< typename T, T _Fist, T _Last >
class Enum
{
public:
class Iterator
{
public:
Iterator( int value ) :
m_value( value )
{ }


T operator*( void ) const
{
return (T)m_value;
}


void operator++( void )
{
++m_value;
}


bool operator!=( Iterator rhs )
{
return m_value != rhs.m_value;
}


private:
int m_value;
};


};


template< typename T, T _Fist, T _Last >
typename Enum<T, _First, _Last >::Iterator begin( Enum<T, _First, _Last> )
{
return typename Enum<T, _First, _Last>::Iterator( (int)_First );
}


template< typename T, T _Fist, T _Last >
typename Enum<T, _First, _Last>::Iterator end( Enum<T, _First, _Last> )
{
return typename Enum<T, _First, _Last>::Iterator( ((int)_Last) + 1 );
}

Extending, but also simplifying the previous answer from @rubenvb (wow, December 2016th already).

To easily iterate over the colors and have a means of providing a numerical or string value to each color (e.g. when you want the values in some Xml file).

enum class COLOR
{
Blue,
Red,
Green,
Purple,
};


std::map<COLOR,std::string> colors = {
{COLOR::Blue,"Blue"},
{COLOR::Red,"Red"},
{COLOR::Green,"Green"},
{COLOR::Purple,"Purple"}, // yay Whoopi, great movie
};


for (auto pair : colors) {
do_something_with_color(pair.first);
and_maybe_even_do_something_with_color_value(pair.second);
}

Maintenance is not even so hard, just make sure you got all your enums in the map.

My two cents: as a complete unashamed hijacking of @matthiascy solution, and coming back to @deft_code philosophy, I introduced default values to _First and _Last template's arguments in order to be able to loop through part of the enum. Doing so, of course, we need again the First and Last in the enum class (which is why this is a hijacking).

template< typename T, T _First = T::First, T _Last= T::Last >
class Enum
{
public:
class Iterator
{
public:
Iterator( int value ) :
m_value( value )
{ }


T operator*( void ) const
{
return (T)m_value;
}


void operator++( void )
{
++m_value;
}


bool operator!=( Iterator rhs )
{
return m_value != rhs.m_value;
}


private:
int m_value;
};


};


template< typename T, T _First = T::First, T _Last= T::Last >
typename Enum<T, _First, _Last >::Iterator begin( Enum<T, _First, _Last> )
{
return typename Enum<T, _First, _Last>::Iterator( (int)_First );
}


template< typename T, T _First = T::First, T _Last= T::Last >
typename Enum<T, _First, _Last>::Iterator end( Enum<T, _First, _Last> )
{
return typename Enum<T, _First, _Last>::Iterator( ((int)_Last) + 1 );
}

For example, to loop through all the pins of an Arduino board:

  for( auto p: Enum<PIN>() ) {
...
}

Or only through the pins of a bus:

  for( auto p: Enum<PIN, PIN::D0, PIN::D6>() ) {
...
}