Разрешить для Range-Based For с перечислением классов?
У меня есть повторяющийся кусок кода, где я перебираю все члены 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 ответов
Итерация перечислений с самим перечислением в качестве итератора - плохая идея, и я рекомендую использовать реальный итератор, как в ответе 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
(Отдельное объявление и определение массива делает его ошибкой компилятора, если количество цветов не соответствует количеству элементов в массиве. Отличная легкая проверка безопасности.)
Лично я не люблю перегружать ++
оператор для перечислений. Часто увеличение значения enum не имеет смысла. Все, что действительно нужно, - это способ итерации по перечислению.
Ниже общий 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;
}
}
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
// ...
}
Я уверен, что вы можете перебирать членов в C++ initializer_list, поэтому я считаю, что делал это в прошлом:
enum class Color {Red, Green, Blue};
for (const Color c : {Color::Red, Color::Green, Color::Blue})
{
}
Есть ли проблемы с этим, я не знаю, но я подумал, что я предложил бы это, поскольку это кратко, но не идеально, если есть много Цветов.
Возможно, вы могли бы сделать что-то умное с 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;
}
Вот проверенный пример (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;
}
Если вы ужасный человек, вы можете получить такое поведение с препроцессором, что-то вроде:
#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) и создание карты констант, например, из. ЦВЕТ:: Синий в "Синий". И наоборот.
Я бы хотел, чтобы стандарт только что встроил эти функции в качестве опций для перечисления классов Ни один из обходных путей не хорош.
Я очень люблю эту идею и часто желаю ее.
Проблема, которую я вижу, состоит в том, что происходит, когда есть повторяющееся числовое значение для элемента перечисления. Все реализации, которые я вижу выше, требуют приведения к целочисленному типу и ++. В конечном счете, я думаю, что языковая поддержка может потребоваться для правильной итерации каждого элемента во всех случаях. Это избавило бы от необходимости иметь First, Last или Begin, End, хотя я не слишком возражаю против этого. Это похоже на поиск begin() end() для контейнеров.
enum class COLOR
{
Blue,
Red,
Green,
Mauve = 0,
Purple,
Last
};
Нумерация начинается с Mauve.
Мои два цента: в качестве полного бессовестного угона решения @matthiascy и возвращаясь к философии @deft_code, я ввел значения по умолчанию для аргументов шаблона _First и _Last, чтобы иметь возможность перебирать часть перечисления. При этом, конечно, нам снова понадобятся First и Last в классе enum (вот почему это угон).
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>() ) {
...
}
Расширение, но также упрощение предыдущего ответа от @rubenvb (вау, уже декабрь 2016 года).
Чтобы легко перебирать цвета и иметь средства предоставления числового или строкового значения для каждого цвета (например, когда вам нужны значения в каком-либо файле 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);
}
Сопровождение даже не так сложно, просто убедитесь, что у вас есть все перечисления на карте.
В качестве модификации ответа @deft_code вам не нужно определять First
и Last
в твоем 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 );
}
Независимо от того, одобряете ли вы добавление перечислений или нет, бывают моменты, когда это полезно. Вот простой способ сделать это:
enum class COLOR
{
Blue,
Red,
Green,
Purple,
First=Blue,
Last=Purple
};
COLOR c;
++( *reinterpret_cast<int*>( &c));
Нет никаких накладных расходов, так как компилятор позаботится о приведении и разыменовании. При необходимости добавьте проверку диапазона или другие возможности.