c++ - example - struct enum
Permitir clases basadas en rangos para con enum? (9)
Aquí hay un ejemplo probado (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;
}
Tengo un fragmento recurrente de código donde recorro todos los miembros de una enum class
.
El bucle for
que uso actualmente parece muy poco manejable en comparación con el nuevo range-based for
.
¿Hay alguna forma de aprovechar las nuevas características de C ++ 11 para reducir la verbosidad de mi ciclo actual?
Código actual que me gustaría mejorar:
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;
}
En otras palabras, sería bueno si pudiera hacer algo como:
for( const auto& c : COLOR )
{
// do work
}
Estoy seguro de que puede iterar sobre los miembros de C ++ initializer_list, así que creo que ya lo hice en el pasado:
enum class Color {Red, Green, Blue};
for (const Color c : {Color::Red, Color::Green, Color::Blue})
{
}
Si hay problemas con esto, no sé, pero pensé que lo recomendaría, ya que es conciso, pero no ideal si hay muchos colores.
La iteración de enumeraciones con la enumeración misma como un iterador es una mala idea, y recomiendo usar un iterador real como en la respuesta de deft_code. Pero si esto es realmente lo que quieres:
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;
}
Trabajando aquí: http://ideone.com/cyTGD8
En el lado iterativo de las cosas, la manera más fácil es simplemente: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;
}
Como se ve aquí: http://ideone.com/9XadVt
(La declaración y definición separada de la matriz hace que sea un error del compilador si la cantidad de colores no coincide con la cantidad de elementos en la matriz. Excelente control de seguridad fácil).
Me gusta mucho la idea y a menudo la he deseado.
El problema que veo es qué sucede cuando hay un valor numérico repetido para un artículo enum. Todas las implementaciones que veo arriba requieren moldes de tipo integral y ++. En última instancia, creo que se podría requerir soporte de lenguaje para iterar verdaderamente sobre cada elemento en todos los casos. Eliminaría la necesidad de tener Primero, Último o Comenzar, Terminar, aunque no me opongo demasiado a esto. Es como buscar begin () end () para contenedores.
enum class COLOR
{
Blue,
Red,
Green,
Mauve = 0,
Purple,
Last
};
La numeración comienza de nuevo en Mauve.
Personalmente, no me gusta sobrecargar al operador ++
para enumeraciones. A menudo, incrementar un valor enum no tiene sentido. Todo lo que realmente se quiere es una forma de iterar sobre la enumeración.
A continuación se muestra una clase Enum
genérica que admite la iteración. Es funcional pero incompleto. Una implementación real haría bien en restringir el acceso al constructor y agregar todos los rasgos del iterador.
#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;
}
}
Probablemente puedas hacer algo inteligente con boost :: mpl, una versión aproximada podría verse así:
#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;
}
Si eres una persona terrible, puedes obtener este comportamiento con el preprocesador, algo así como:
#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);
}
}
Con pequeñas modificaciones, esto también podría ser útil, por ej. ENUM_SETVALUE (Azul, 4) y haciendo un mapa const desde, p. Ej. COLOR :: Azul a "Azul". Y viceversa.
Desearía que el estándar acabara de incorporar estas características como opciones para la clase enum. Ninguna de las soluciones alternativas es buena.
Ya sea que apruebes o no el aumento de enumeraciones, hay ocasiones en que es útil. Así que aquí hay una manera simple de hacerlo:
enum class COLOR
{
Blue,
Red,
Green,
Purple,
First=Blue,
Last=Purple
};
COLOR c;
++( *reinterpret_cast<int*>( &c));
No hay sobrecarga, ya que el compilador se encargará de la fundición y la desreferenciación. Agregue verificación de rango u otras capacidades según sea necesario.
enum class Color {
blue,
red,
green = 5,
purple
};
const std::array<Color,4> all_colors = {Color::blue, Color::red, Color::green, Color::purple};
Entonces:
for (Color c : all_colors) {
//...
}
Muchas veces lo uso así, donde quiero un valor ''ninguno'':
// 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
// ...
}