【问题标题】:Allow for Range-Based For with enum classes?允许枚举类的基于范围的 For?
【发布时间】:2012-01-19 20:42:18
【问题描述】:

我有一段重复的代码,我循环遍历 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
}

【问题讨论】:

  • 有趣。自 1983 年以来,Ada 就有此功能。
  • (COLOR)(((int)(x) + 1)) 而不是int,请考虑使用std::underlying_type<COLOR>::type
  • 预计会跳过 Purple 吗?
  • @kfmfe04 std::underlying_typenot supported on GCC 4.6。它将在 4.7 上得到支持。这里有一个近似的模拟:stackoverflow.com/a/7932617/46642.
  • 啊!这么快就接受了。我通常会等待 24 小时,以便让所有时区都有机会提出更好的答案。

标签: c++ for-loop c++11 enums


【解决方案1】:

我个人不喜欢为枚举重载++ 运算符。通常递增一个枚举值并没有什么意义。真正想要的只是一种迭代枚举的方法。

下面是一个支持迭代的通用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 很好 - 我已经修改了自己的 Enum 类以遵循您的格式(不在 OP 中)。类型安全性很好,但T from_string( const string&amp; T ) 有点像在 C++ 中的痛苦,我们不能在返回枚举值上重载。无论我是否使用模板枚举都存在同样的问题,但是使用模板枚举,它只是有点冗长。
  • Working complete implementation,还请注意,我将Last 的值更改为与正常的迭代器范围更加一致。
【解决方案2】:
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
    // ...
}

【讨论】:

  • 这是一个很好的答案!将项目放在两个地方的冗余很遗憾,但它比其他解决方案更清洁;这没有强制转换,正如您提到的,这些值不必从零开始或连续。您还可以轻松地指定值的子集,例如深色和浅色。它有点依赖具有相当少量值的枚举,但它们通常还是会这样做。
  • 您的答案的附录:使用此解决方案,您可以使用 colors.size() 获取值的计数。如果您需要一个编译时间常数(例如,对于另一个数组的大小),您可以使用 std::tuple_size(decltype(colors))::value,这无疑有点冗长但完全安全。
  • 为什么数组的大小指定为3?不应该是2吗?如果是这样,这不是说明违反 DRY 原则总是会导致难以发现的错误吗?
  • 如果有人增加了价值真的很糟糕。比你的数组没有覆盖所有值!!
【解决方案3】:

使用枚举本身作为迭代器来迭代枚举是一个糟糕的主意,我建议在 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


在迭代器方面,最简单的方法是:
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;
}

如此处所示:http://coliru.stacked-crooked.com/a/5d356cc91556d6ef

(如果颜色的数量与数组中的元素数量不匹配,则单独的定义和数组的引用会导致编译器错误。非常容易进行安全检查。)

【讨论】:

  • 需要一个 operator* 来使 COLOR 成为输入迭代器。
  • @R.MartinhoFernandes 这个operator* 的签名应该是什么样的?
  • @kfmfe04 我将其添加到答案中。
  • @R.MartinhoFernandes:谢谢,我无法使用基于范围的 for 循环访问编译器。虽然我开始意识到我应该使用实际的迭代器来迭代而不是颜色本身。这可能是更好的答案。 ++c 是否与枚举 class 一起使用?可能不会……
  • @MooingDuck ++ 不适用于强类型枚举,但 kfmfe04 对他的问题有一个实现。
【解决方案4】:

你可以用 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++ 初始化器列表的成员,所以我认为我过去曾这样做过:

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

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

    【讨论】:

    • 这可行,但不便于维护。考虑何时添加新枚举。我们用 begin 和 end 标记我们的枚举(例如 eCol0rBegin = 0 和 eColorEnd),最好您希望代码在任何地方自动拾取它。
    • 是的@gast128 - 你是对的。如果这将是一个潜在的问题,那么我会在枚举中添加一个结束标记,例如: enum class Color {Red, Green, Blue, Count};然后我可以静态断言 Count 没有改变。
    【解决方案6】:

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

    【讨论】:

    • 这也适用于 Linux 上的 Intel C++ 2013 SP1,当您安装更新 1 时无法编译,我们向 Intel Engineering 报告了一个问题。
    【解决方案7】:

    如果你是一个很糟糕的人,你可以通过预处理器获得这种行为,例如:

    #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) 并从例如制作一个 const 映射。颜色::蓝色到“蓝色”。反之亦然。

    我希望标准刚刚将这些功能作为枚举类的选项构建。没有一种解决方法是好的。

    【讨论】:

      【解决方案8】:

      我非常喜欢这个想法,并且经常希望它。

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

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

      编号从淡紫色开始。

      【讨论】:

      • 是的,或者有差距的时候。
      【解决方案9】:

      我的两分钱:作为对@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>() ) {
          ...
        }
      

      【讨论】:

      • 非常有趣的想法!
      【解决方案10】:

      无论您是否赞成增加枚举,有时它是有用的。所以这里有一个简单的方法:

      enum class COLOR
      {
          Blue,
          Red,
          Green,
          Purple,
          First=Blue,
          Last=Purple
      };
      
      COLOR c;
      
      ++( *reinterpret_cast<int*>( &c));
      

      没有开销,因为编译器会处理强制转换和取消引用。根据需要添加范围检查或其他功能。

      【讨论】:

        【解决方案11】:

        作为@deft_code 答案的修改,您无需在enum class 中定义FirstLast,只需为模板化的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 );
        }
        

        【讨论】:

          【解决方案12】:

          扩展,但也简化了@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);
          }
          

          维护甚至不是那么难,只要确保你的所有枚举都在地图中。

          【讨论】:

            猜你喜欢
            • 2021-08-30
            • 2019-08-29
            • 1970-01-01
            • 2023-03-29
            • 2019-06-15
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多