2009-09-07 23 views
37

En C++, ¿es posible enumerar sobre una enumeración (tiempo de ejecución o tiempo de compilación (preferido)) y llamar funciones/generar código para cada iteración?Enumerar sobre una enumeración en C++

caso de uso

muestra:

enum abc 
{  
    start 
    a, 
    b, 
    c, 
    end 
}  
for each (__enum__member__ in abc) 
{  
    function_call(__enum__member__);  
} 

duplicados plausibles:

+0

¿Cómo se supone que debe elegir la función para llamar? ¿Podrías publicar algún pseudo código como supones que lo harías? Podría ayudarnos a ayudarlo. –

+0

actualizado para krill – jameszhao00

+0

Para ver el tiempo de ejecución en http://stackoverflow.com/questions/1292426/is-there-any-well-known-paradigm-for-iterating-through-enum. (Si no fuera por tiempo de compilación, su pregunta sería un duplicado exacto de ella.) – sbi

Respuesta

54

Para añadir a @StackedCrooked respuesta, puede sobrecargar operator++, operator-- y operator* y tienen iterador como la funcionalidad. prueba

enum Color { 
    Color_Begin, 
    Color_Red = Color_Begin, 
    Color_Orange, 
    Color_Yellow, 
    Color_Green, 
    Color_Blue, 
    Color_Indigo, 
    Color_Violet, 
    Color_End 
}; 

namespace std { 
template<> 
struct iterator_traits<Color> { 
    typedef Color value_type; 
    typedef int difference_type; 
    typedef Color *pointer; 
    typedef Color &reference; 
    typedef std::bidirectional_iterator_tag 
    iterator_category; 
}; 
} 

Color &operator++(Color &c) { 
    assert(c != Color_End); 
    c = static_cast<Color>(c + 1); 
    return c; 
} 

Color operator++(Color &c, int) { 
    assert(c != Color_End); 
    ++c; 
    return static_cast<Color>(c - 1); 
} 

Color &operator--(Color &c) { 
    assert(c != Color_Begin); 
    return c = static_cast<Color>(c - 1); 
} 

Color operator--(Color &c, int) { 
    assert(c != Color_Begin); 
    --c; 
    return static_cast<Color>(c + 1); 
} 

Color operator*(Color c) { 
    assert(c != Color_End); 
    return c; 
} 

Vamos con un poco de <algorithm> plantilla

void print(Color c) { 
    std::cout << c << std::endl; 
} 

int main() { 
    std::for_each(Color_Begin, Color_End, &print); 
} 

Ahora, Color es un iterador bidireccional constante. Aquí hay una clase reutilizable que codifiqué mientras lo hacía manualmente arriba. Me di cuenta de que podría funcionar para muchas más enumeraciones, por lo que repetir el mismo código de nuevo es bastante tedioso

// Code for testing enum_iterator 
// -------------------------------- 

namespace color_test { 
enum Color { 
    Color_Begin, 
    Color_Red = Color_Begin, 
    Color_Orange, 
    Color_Yellow, 
    Color_Green, 
    Color_Blue, 
    Color_Indigo, 
    Color_Violet, 
    Color_End 
}; 

Color begin(enum_identity<Color>) { 
    return Color_Begin; 
} 

Color end(enum_identity<Color>) { 
    return Color_End; 
} 
} 

void print(color_test::Color c) { 
    std::cout << c << std::endl; 
} 

int main() { 
    enum_iterator<color_test::Color> b = color_test::Color_Begin, e; 
    while(b != e) 
    print(*b++); 
} 

implantación sigue.

template<typename T> 
struct enum_identity { 
    typedef T type; 
}; 

namespace details { 
void begin(); 
void end(); 
} 

template<typename Enum> 
struct enum_iterator 
    : std::iterator<std::bidirectional_iterator_tag, 
        Enum> { 
    enum_iterator():c(end()) { } 

    enum_iterator(Enum c):c(c) { 
    assert(c >= begin() && c <= end()); 
    } 

    enum_iterator &operator=(Enum c) { 
    assert(c >= begin() && c <= end()); 
    this->c = c; 
    return *this; 
    } 

    static Enum begin() { 
    using details::begin; // re-enable ADL 
    return begin(enum_identity<Enum>()); 
    } 

    static Enum end() { 
    using details::end; // re-enable ADL 
    return end(enum_identity<Enum>()); 
    } 

    enum_iterator &operator++() { 
    assert(c != end() && "incrementing past end?"); 
    c = static_cast<Enum>(c + 1); 
    return *this; 
    } 

    enum_iterator operator++(int) { 
    assert(c != end() && "incrementing past end?"); 
    enum_iterator cpy(*this); 
    ++*this; 
    return cpy; 
    } 

    enum_iterator &operator--() { 
    assert(c != begin() && "decrementing beyond begin?"); 
    c = static_cast<Enum>(c - 1); 
    return *this; 
    } 

    enum_iterator operator--(int) { 
    assert(c != begin() && "decrementing beyond begin?"); 
    enum_iterator cpy(*this); 
    --*this; 
    return cpy; 
    } 

    Enum operator*() { 
    assert(c != end() && "cannot dereference end iterator"); 
    return c; 
    } 

    Enum get_enum() const { 
    return c; 
    } 

private: 
    Enum c; 
}; 

template<typename Enum> 
bool operator==(enum_iterator<Enum> e1, enum_iterator<Enum> e2) { 
    return e1.get_enum() == e2.get_enum(); 
} 

template<typename Enum> 
bool operator!=(enum_iterator<Enum> e1, enum_iterator<Enum> e2) { 
    return !(e1 == e2); 
} 
+3

+1: Esto es realmente elegante. – ereOn

4

Ni es posible sin un littl e trabajo manual. Mucho del trabajo se puede hacer con macros, si está dispuesto a profundizar en esa área.

1

Sin

Sin embargo, se podría definir su propia clase que implementa funciones de enumeración similar con iteraciones. Puede recordar un truco de los anteriores días de Java de 1.5, llamado "patrón de diseño de enum seguro". Podrías hacer el equivalente de C++.

2

Ampliando lo que dice Konrad, una posible expresión idiomática en el caso de "generar código para cada iteración" es el uso de un archivo incluido para representar la enumeración:

mystuff.h:

#ifndef LAST_ENUM_ELEMENT 
#define LAST_ENUM_ELEMENT(ARG) ENUM_ELEMENT(ARG) 
#endif 

ENUM_ELEMENT(foo) 
ENUM_ELEMENT(bar) 
LAST_ENUM_ELEMENT(baz) 

// not essential, but most likely every "caller" should do it anyway... 
#undef LAST_ENUM_ELEMENT 
#undef ENUM_ELEMENT 

enum.h:

// include guard goes here (but mystuff.h doesn't have one) 

enum element { 
    #define ENUM_ELEMENT(ARG) ARG, 
    #define LAST_ENUM_ELEMENT(ARG) ARG 
    #include "mystuff.h" 
} 

main.cpp:

#include "enum.h" 
#define ENUM_ELEMENT(ARG) void do_##ARG(); 
#include "mystuff.h" 

element value = getValue(); 
switch(value) { 
    #define ENUM_ELEMENT(ARG) case ARG: do_##ARG(); break; 
    #include "mystuff.h" 
    default: std::terminate(); 
} 

Por lo tanto, para agregar un nuevo elemento "qux", lo agrega a mystuff.h y escribe la función do_qux. No tiene que tocar el código de envío.

Por supuesto, si los valores en su enumeración deben ser números enteros no consecutivos específicos, entonces termina manteniendo la definición de enumeración y la lista ENUM_ELEMENT(foo) ... por separado, lo cual es desordenado.

1

Esto parece hacky para mí, pero puede adaptarse a sus propósitos:

enum Blah { 
    FOO, 
    BAR, 
    NUM_BLAHS 
}; 

// later on 
for (int i = 0; i < NUM_BLAHS; ++i) { 
    switch (i) { 
    case FOO: 
    // foo stuff 
    break; 
    case BAR: 
    // bar stuff 
    break; 
    default: 
    // you're missing a case statement 
    } 
} 

Si necesita un valor de inicio especial, puede hacer que una constante y la puso en su enumeración. No revisé si esto compila, pero debería estar cerca de estar allí :-). Espero que esto ayude.

Creo que este enfoque podría ser un buen equilibrio para su caso de uso. Úselo si no necesita hacer esto para un grupo de diferentes tipos enumerados y no quiere lidiar con cosas del preprocesador. Solo asegúrate de comentar y probablemente agregar TODO para cambiarlo en una fecha posterior a algo mejor :-).

+4

El 'for 'y el' interruptor 'anidado son totalmente inútiles/sin sentido ya que de todos modos usa cada uno de los valores consecutivos. Simplemente omita ambos y ejecute 'foo stuff', seguido de' bar stuff', etc. directamente. –

+0

Esto solo es útil en un programa de prueba donde ejecuta una serie de pruebas. Las condiciones de inicio/final serían variables, por lo que si solo desea ejecutar una prueba, configure start = end: for (int i = start; i <= end; ++ i) {....} En todos los demás casos, Estoy de acuerdo con Konrad: cambiar dentro de a es probablemente un mal diseño. – jmucchiello

+2

¿Un bucle for golpea cada caso de una declaración de cambio? Por favor. Esto es Stackoverflow, no thedailywtf.com. Es solo una forma innecesariamente complicada de hacer "cosas tontas", luego "cosas de bar". Eso no requiere ningún tipo de control de flujo. – Alan

42

Actualmente, C++ no proporciona la iteración del enumerador. A pesar de eso, a veces surge la necesidad de esto. Una solución común es agregar valores que marcan el comienzo y el final.Por ejemplo:

enum Color 
{ 
    Color_Begin, 
    Color_Red = Color_Begin, 
    Color_Orange, 
    Color_Yellow, 
    Color_Green, 
    Color_Blue, 
    Color_Indigo, 
    Color_Violet, 
    Color_End 
}; 

void foo(Color c) 
{ 
} 


void iterateColors() 
{ 
    for (size_t colorIdx = Color_Begin; colorIdx != Color_End; ++colorIdx) 
    { 
     foo(static_cast<Color>(colorIdx)); 
    } 
} 
0

puede realizar algunas de las técnicas de ejecución propuestos estáticamente con TMP.

#include <iostream> 

enum abc 
{ 
    a, 
    b, 
    c, 
    end 
}; 

void function_call(abc val) 
{ 
    std::cout << val << std::endl; 
} 

template<abc val> 
struct iterator_t 
{ 
    static void run() 
    { 
     function_call(val); 

     iterator_t<static_cast<abc>(val + 1)>::run(); 
    } 
}; 

template<> 
struct iterator_t<end> 
{ 
    static void run() 
    { 
    } 
}; 

int main() 
{ 
    iterator_t<a>::run(); 

    return 0; 
} 

La salida de este programa es:

0 
1 
2 

Ver Ch 1 de Abrahams, Gurtovoy "C++ Plantilla Metaprogramación" para un buen tratamiento de esta técnica. La ventaja de hacerlo de esta manera sobre las técnicas de ejecución propuesto es que, al optimizar el código, puede inline la estática y es más o menos equivalente a:

function_call(a); 
function_call(b); 
function_call(c); 

Inline function_call para aún más la ayuda del compilador.

Las mismas críticas de otras técnicas de iteración de enumeración se aplican aquí. Esta técnica solo funciona si su enumeración aumenta continuamente de uno a otro.

1

por lo general lo hago de esta manera:

enum abc 
{  
    abc_begin, 
    a = abc_begin, 
    b, 
    c, 
    abc_end 
}; 

void foo() 
{ 
    for(auto&& r : range(abc_begin,abc_end)) 
    { 
     cout << r; 
    } 
} 


range es completamente genérico, y se define como sigue:

template <typename T> 
class Range 
{ 
public: 
    Range(const T& beg, const T& end) : b(beg), e(end) {} 
    struct iterator 
    { 
     T val; 
     T operator*() { return val; } 
     iterator& operator++() { val = (T)(1+val); return *this; } 
     bool operator!=(const iterator& i2) { return val != i2.val; } 
    }; 
    iterator begin() const { return{b}; } 
    iterator end() const { return{e}; } 
private: 
    const T& b; 
    const T& e; 
}; 

template <typename T> 
Range<T> range(const T& beg, const T& end) { return Range<T>(beg,end); } 
0

Amor plantillas pero voy a tomar nota de esto para mi uso futuro/de otras personas para que no nos perdamos con ninguno de los anteriores.

Las enumeraciones son convenientes para comparar cosas de una manera conocida y ordenada. Normalmente se usan codificados de forma rígida en funciones por razones de legibilidad frente a valores enteros. Algo similar a las definiciones de los preprocesadores, con la excepción de que no se reemplazan con literales, sino que se mantienen y acceden en tiempo de ejecución.

Si tuviéramos una enumeración que define los códigos de error HTML y sabíamos que los códigos de error en el 500 son los errores de servidor, puede ser que sea más agradable para leer algo como:

enum HtmlCodes {CONTINUE_CODE=100,CLIENT_ERROR=400,SERVER_ERROR=500,NON_STANDARD=600}; 

if(errorCode >= SERVER_ERROR && errorCode < NON_STANDARD) 

que

if(errorCode >= 500 && errorCode < 600) 

La parte clave es esta, ¡son similares a las matrices! sino que sirven paraconvertir valoresnúmero entero.

ejemplo corto:

enum Suit {Diamonds, Hearts, Clubs, Spades}; 
//does something with values in the enum past "Hearts" in this case 
for(int i=0;i<4;i++){ 
    //Could also use i or Hearts, because the enum will turns these both back into an int 
    if((Suit)(i) > 1) 
    { 
     //Whatever we'd like to do with (Suit)(i) 
    } 
} 

A menudo las enumeraciones se utilizan también con char * arrays o matrices de cadenas de modo que se podía imprimir algún mensaje con el valor asociado. Normalmente sólo están matrices con el mismo conjunto de valores de la enumeración, así:

char* Suits[4] = {"Diamonds", "Hearts", "Clubs", "Spades"}; 
//Getting a little redundant 
cout << Suits[Clubs] << endl; 
//We might want to add this to the above 
//cout << Suits[(Suit)(i)] << endl; 

Y por supuesto que es aún más agradable para crear una clase genérica que se ocupa de iteración para enumeraciones como las respuestas anteriores.