Разрешить для Range-Based For с перечислением классов? - PullRequest
64 голосов
/ 14 декабря 2011

У меня есть периодический кусок кода, где я перебираю все элементы 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
}

Ответы [ 9 ]

49 голосов
/ 14 декабря 2011

Мне лично не нравится перегрузка оператора ++ для перечислений.Часто увеличение значение перечисления на самом деле не имеет смысла.Все, что действительно нужно, - это способ итерации по перечислению.

Ниже приведен общий класс 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;
   }
}
33 голосов
/ 09 апреля 2014
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) {
    //...
}

Много раз я использую это так, где я хочу значение 'none':

// 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
    // ...
}
30 голосов
/ 14 декабря 2011

Итерация перечислений с самим перечислением в качестве итератора - плохая идея, и я рекомендую использовать реальный итератор, как в ответе 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 голосов
/ 14 декабря 2011

Вы могли бы сделать что-то умное с 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;
}
3 голосов
/ 14 декабря 2011

Вот проверенный пример (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;
}
2 голосов
/ 30 июня 2018

Независимо от того, одобряете ли вы добавление перечислений или нет, бывают моменты, когда это полезно.Итак, вот простой способ сделать это:

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

COLOR c;

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

Нет никаких накладных расходов, так как компилятор позаботится о приведении и разыменовании.При необходимости добавьте проверку диапазона или другие возможности.

2 голосов
/ 03 октября 2017

Я уверен, что вы можете перебирать членов в C ++ initializer_list, поэтому я считаю, что я делал это в прошлом:

enum class Color {Red, Green, Blue};

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

Есть ли проблемы с этим, я не знаю, но я подумал, что я бы предложил это, поскольку это кратко, но не идеально, если есть много Цветов.

2 голосов
/ 29 апреля 2015

Если вы ужасный человек, вы можете получить такое поведение с препроцессором, что-то вроде:

#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 (синий, 4) и создание карты констант из, например.ЦВЕТ :: Синий в «Синий».И наоборот.

Хотелось бы, чтобы стандарт только что встроил эти функции в качестве опций для перечисления классов.Ни один из обходных путей не подходит.

2 голосов
/ 14 декабря 2011

Мне очень нравится эта идея, и я часто желал ее.

Проблема, которую я вижу, заключается в том, что происходит, когда для элемента перечисления есть повторяющееся числовое значение. Все реализации, которые я вижу выше, требуют приведения к целочисленному типу и ++. В конечном счете, я думаю, что языковая поддержка может потребоваться для правильной итерации каждого элемента во всех случаях. Это избавило бы от необходимости иметь First, Last или Begin, End, хотя я не слишком возражаю против этого. Это похоже на поиск begin () end () для контейнеров.

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

Нумерация начинается с Mauve.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...