Разрешить диапазон на основе классов с перечислением?
у меня есть рекуррентный кусок кода, где я перебираю все членыenum class
.
The 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 // ... }
вы, вероятно, могли бы сделать что-то умное с 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; }
Мне очень нравится эта идея, и я часто желал ее.
проблема, которую я вижу, заключается в том, что происходит, когда есть повторяющееся числовое значение для элемента перечисления. Все реализации, которые я вижу выше, требуют приведения к интегральному типу и ++. В конечном счете, я думаю, что языковая поддержка может потребоваться для истинного перебора каждого элемента во всех случаях. Это устранило бы необходимость иметь первый, последний или начать, конец, хотя я не возражаю против этого слишком много. Это как искать начало () конец () для стеклотара.
enum class COLOR { Blue, Red, Green, Mauve = 0, Purple, Last };
нумерация начинается с сиреневого цвета.
Я уверен, что вы можете перебирать члены c++ initializer_list, поэтому я считаю, что я сделал это в прошлом:
enum class Color {Red, Green, Blue}; for (const Color c : {Color::Red, Color::Green, Color::Blue}) { }
есть ли проблемы с этим, я не знаю, но я думал, что предложу его, поскольку он лаконичен, но не идеален, если есть много цветов.
Если вы ужасный человек, вы можете получить такое поведение с препроцессором, что-то вроде:
#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) и создание карты const, например. Цвет: от синего до"синего". И наоборот.
Я хотел бы, чтобы стандарт только что построил эти функции в качестве опций для перечисления класса. Ни один из способов хорошо.
независимо от того, Одобряете ли вы увеличение перечислений, бывают случаи, когда это полезно. Итак, вот простой способ сделать это:
enum class COLOR { Blue, Red, Green, Purple, First=Blue, Last=Purple }; COLOR c; ++( *reinterpret_cast<int*>( &c));
нет никаких накладных расходов, так как компилятор будет заботиться о приведении и снятии ссылок. Добавьте проверку диапазона или другие возможности по мере необходимости.