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


73

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

for与新的相比,我目前使用的循环看起来非常笨拙range-based 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
}

17
有趣。自1983
。– Shark8 2011年

8
(COLOR)(((int)(x) + 1))而不是int考虑使用std::underlying_type<COLOR>::type
James McNellis 2011年

8
预计紫色会被跳过吗?
kennytm 2011年

4
@ kfmfe04std::underlying_type不支持GCC 4.6。4.7将支持它。这里有一个近似的仿真:stackoverflow.com/a/7932617/46642
R. Martinho Fernandes

5
加!这么快接受。我通常会等待24小时,以便为所有时区提供一个更好的答案。
deft_code 2011年

Answers:


33

用枚举本身作为迭代器来迭代枚举是一个糟糕的主意,我建议像在deft_code的答案中那样使用实际的迭代器。但是,如果这确实是您想要的:

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;
}

在这里工作:http//ideone.com/cyTGD8


在迭代器方面,最简单的方法是:

extern const COLOR COLORS[(int)COLOR::Last+1];
const COLOR COLORS[] = {COLOR::Blue, COLOR::Red, COLOR::Green, COLOR::Purple};

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

如此处所示:http : //ideone.com/9XadVt

(如果颜色的数量与数组中元素的数量不匹配,则数组的单独声明和定义使它成为编译器错误。非常容易进行安全检查。)


4
需要一个operator*使COLOR一个输入迭代器。
R. Martinho Fernandes

@ R.MartinhoFernandes的签名应该是什么operator*样的?
kfmfe04 2011年

3
@ kfmfe04我将其添加到答案中。
R. Martinho Fernandes

@ R.MartinhoFernandes:谢谢,我没有基于范围的for循环的访问编译器。尽管我开始想到应该使用实际的迭代器进行迭代,而不是使用颜色本身。那可能是更好的答案。++ c是否可以使用枚举class?可能不是…
Mooing Duck 2011年

2
@MooingDuck ++不适用于强类型的枚举,但是kfmfe04对此问题有一个实现。
R. Martinho Fernandes

55

我个人不喜欢++为枚举重载运算符。经常递增枚举值实际上没有任何意义。真正需要的只是一种迭代枚举的方法。

下面是一个Enum支持迭代的通用类。功能正常,但不完整。真正的实现会很好地限制对构造函数的访问并添加所有迭代器特征。

#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;
   }
}

1
同意 这比在枚举类型本身上重载运算符要好得多。
James McNellis 2011年

+1不错-我已经修改了自己的Enum类以遵循您的格式(不是OP中的格式)。类型安全性很好,但是T from_string( const string& T )像在C ++中那样有点痛苦,我们不能在返回枚举值上重载。无论是否使用template-Enum,都存在相同的问题,但是对于template-Enum,它只是比较冗长。
kfmfe04 2011年

10
在完成工作的过程中,还请注意,我将的值更改为Last与常规迭代器范围更加一致。
Mooing Duck 2013年

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

然后:

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

我多次这样使用它,在这里我想要一个“无”值:

// 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
    // ...
}

10
这是一个很好的答案!可惜的是,将物品放在两个地方是多余的,但这是比其他解决方案更清洁的解决方案。这没有强制转换,并且正如您提到的,值不必从零开始或连续。您还可以轻松指定值的子集,例如darkColors和lightColors。它有点依赖于枚举值数量很少的枚举,但无论如何它们通常都会起作用。
Jim Oldfield 2014年

答案的附录:使用此解决方案,您可以使用colors.size()获得值的计数。如果您需要一个编译时间常数(例如,对于另一个数组的大小),则可以使用std :: tuple_size(decltype(colors)):: value,它虽然有点长,但绝对安全。
Jim Oldfield

3
为什么将数组的大小指定为3?不是2吗?如果是这样,这是否说明违反DRY原理总是导致难以发现的错误?
Felix Dombek '18

1
如果有人增加了价值,那真的很糟糕。比您的数组不能涵盖所有值!!
DrumM

6

您可能可以使用boost :: mpl做一些聪明的事情,一个粗糙的版本可能看起来像:

#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;
}

5

我确定您可以遍历C ++ initializer_list的成员,因此我认为我过去已经这样做过:

enum class Color {Red, Green, Blue};

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

我不知道这是否有问题,但是我想我建议这样做是因为它很简洁,但是如果有很多颜色,那是不理想的。


这可行,但不便于维护。考虑何时添加新的枚举。我们用开始和结束标记我们的枚举(例如eCol0rBegin = 0和eColorEnd),最好您希望代码在任何地方自动将其拾取。
gast128

是@ gast128-您是对的。如果那将是一个潜在的问题,那么我将在枚举中添加一个结束标记,例如:枚举类Color {Red,Green,Blue,Count}; 然后,我可以static_assert Count保持不变。
Coder_Dan

4

这是一个经过测试的示例(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;
}

1
这也适用于Linux上的Intel C ++ 2013 SP1,当您安装Update 1时无法编译,我们为此向Intel Engineering报告了一个问题。
约翰尼·威廉森

4

如果您是一个可怕的人,则可以通过预处理器获得此行为,例如:

#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);
    }
}

稍作修改,这也可以支持例如。ENUM_SETVALUE(Blue,4)并从例如 COLOR :: Blue到“ Blue”。反之亦然。

我希望标准只是将这些功能构建为枚举类的选项。没有一种变通办法是好的。


2

我非常喜欢这个主意,并且经常希望它。

我看到的问题是,枚举项目重复数值时会发生什么。我在上面看到的所有实现都需要强制转换为整数类型和++。最终,我认为在所有情况下都可能需要语言支持才能真正遍历每个项目。尽管我不反对太多,但这将消除了具有First,Last或Begin,End的需要。就像在寻找容器的begin()end()一样。

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

编号从淡紫色开始。


2
是的,还是有差距的时候。
Lightness Races in Orbit

1

在某些情况下,是否批准枚举递增是有用的。因此,这是一种简单的方法:

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

COLOR c;

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

由于编译器将进行强制转换和取消引用,因此没有开销。根据需要添加范围检查或其他功能。


1

作为@ deft_code的答案的修改,你不需要定义FirstLast你的enum class,只需添加两个参数的模板Enum类。

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 );
}

1

我的两分钱:作为对@matthiascy解决方案的完整劫持,并返回到@deft_code哲学,我为_First和_Last模板的参数引入了默认值,以便能够遍历部分枚举。这样做,当然,我们再次需要枚举类中的First和Last(这就是为什么这是劫持的原因)。

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 );
}

例如,要遍历Arduino板的所有引脚:

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

或仅通过总线的引脚:

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

0

扩展但也简化了@rubenvb的先前答案(哇,已经于2016年12月发布)。

为了轻松地遍历颜色提供一种为每种颜色提供数字或字符串值的方式(例如,当您需要某个Xml文件中的值时)。

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);
}

维护工作并不难,只需确保将所有枚举都包含在地图中即可。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.