2009-09-07 19 views
37

In C++, è possibile enumerare su un enum (tempo di esecuzione o di compilazione (preferito)) e chiamare funzioni/generare codice per ogni iterazione?Enumera su un enum in C++

Esempio caso d'uso:

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

duplicati plausibili:

+0

Come si suppone di selezionare la funzione per la chiamata? Potresti postare qualche pseudo codice come pensi di farlo? Potrebbe aiutarci ad aiutarti. –

+0

aggiornato per krill – jameszhao00

+0

Per runtime, consultare http://stackoverflow.com/questions/1292426/is-there-any-well-known-paradigm-for-iterating-through-enum. (Se non fosse in fase di compilazione, la tua domanda sarebbe un duplicato esatto.) – sbi

risposta

54

Per aggiungere al @StackedCrooked risposta, è possibile sovraccaricare operator++, operator-- e operator* e hanno iteratore come funzionalità. Test

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; 
} 

Let con qualche <algorithm> modello

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

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

Ora, Color è un iteratore bidirezionale costante. Ecco una classe riusabile che ho codificato mentre lo faccio manualmente sopra. Ho notato che potrebbe funzionare per molti più enumerazioni, in modo da ripetere lo stesso codice di nuovo è abbastanza noioso

// 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++); 
} 

attuazione segue.

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: questo è davvero elegante. – ereOn

4

Né è possibile senza una littl e lavoro manuale. Un sacco di lavoro può essere fatto con macro, se sei disposto a scavare in quella zona.

1

No

Tuttavia, si potrebbe definire la propria classe che implementa funzioni di enum-come con iterazioni. Si può ricordare un trucco dei pre 1.5 giorni Java, chiamato "tipo safe enum design pattern". Potresti fare l'equivalente in C++.

2

Ampliando ciò che dice Konrad, un possibile linguaggio, nel caso di "generare il codice per ogni iterazione" è quello di utilizzare un file incluso per rappresentare l'enumerazione:

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(); 
} 

Quindi, per aggiungere un nuovo elemento "qux", lo aggiungi a mystuff.h e scrivi la funzione do_qux. Non è necessario toccare il codice di spedizione.

Ovviamente se i valori dell'enumerazione devono essere interi specifici non consecutivi, si finisce per mantenere la definizione enum e l'elenco ENUM_ELEMENT(foo) ... che è disordinato.

1

Questo sembra hacky per me, ma potrebbe soddisfare i vostri scopi:

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 
    } 
} 

Se avete bisogno di un valore iniziale speciale, si può fare che una costante e metterlo nel vostro enum. Non ho controllato se questo compila, ma dovrebbe essere vicino ad essere lì :-). Spero che questo ti aiuti.

Penso che questo approccio potrebbe essere un buon equilibrio per il tuo caso d'uso. Usalo se non hai bisogno di farlo per un gruppo di tipi enumerati diversi e non vuoi occuparti del materiale del preprocessore. Assicurati di commentare e probabilmente aggiungi un TODO per cambiarlo in un secondo momento con qualcosa di meglio :-).

+4

Gli switch 'for' e nested' sono completamente inutili/privi di significato poiché si utilizzano comunque ciascuno dei valori consecutivi. Basta omettere entrambi ed eseguire 'foo stuff', seguito da' bar stuff' ecc. Direttamente. –

+0

Ciò è utile solo in un programma di test in cui si eseguono una serie di test. Le condizioni di inizio/fine sarebbero variabili, quindi se si vuole eseguire solo un test si imposta start = end: for (int i = start; i <= end; ++ i) {....} In tutti gli altri casi, Sono d'accordo con Konrad: passare dentro a per è probabilmente un cattivo progetto. – jmucchiello

+2

Un ciclo for per colpire ogni caso di un'istruzione switch? Per favore. Questo è StackOverflow, non thedailywtf.com. Questo è solo un modo inutilmente complicato per fare "cose ​​da fare", quindi "cose ​​da bar". Ciò non richiede alcun tipo di controllo del flusso. – Alan

42

Attualmente C++ non fornisce l'iterazione dell'enumeratore. Nonostante ciò, a volte sorge la necessità di questo. Una soluzione alternativa è aggiungere valori che contrassegnano l'inizio e il finale.Per esempio:

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

È possibile eseguire in modo statico alcune delle tecniche di runtime proposte 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; 
} 

L'output di questo programma è:

0 
1 
2 

Sede Ch 1 di Abrahams, Gurtovoy "Template Metaprogrammazione C++" per un buon trattamento di questa tecnica. Il vantaggio di fare in questo modo oltre alle tecniche di esecuzione proposti è che, quando si ottimizzare questo codice, è possibile inline la statica ed è più o meno equivalente a:

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

Inline function_call per ancora più aiuto da parte del compilatore.

Le stesse critiche ad altre tecniche di iterazione di enumerazione si applicano qui. Questa tecnica funziona solo se l'enumerazione aumenta continuamente da una parte alla fine di una.

1

faccio di solito che in questo modo:

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

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


range è del tutto generico, e definito come segue:

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

Amore di template, ma ho intenzione di prendere nota di questo per il mio futuro/uso di altre persone, quindi non siamo persi con nessuno dei precedenti.

Le enumerazioni sono convenienti per il confronto di cose in modo noto e ordinato. In genere vengono utilizzati con codifica hard in funzioni per motivi di leggibilità rispetto ai valori interi. Un po 'simile alle definizioni del preprocessore, con l'eccezione che non vengono sostituiti con letterali, ma mantenuti e accessibili in runtime.

Se avessimo un enum che definisce i codici di errore html e sapevamo che i codici di errore nelle 500s sono errori del server, potrebbe essere più bello di leggere qualcosa di simile:

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

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

di

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

La parte fondamentale è questa, sono simili agli array! Ma sono utilizzati percastvalori interi.

breve esempio:

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) 
    } 
} 

Spesso enumerazioni sono utilizzati anche con char * array o array di stringhe in modo da poter stampare qualche messaggio con il valore associato. Normalmente sono solo le matrici con lo stesso insieme di valori nella enum, in questo modo:

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; 

E, naturalmente, è ancora più bello per creare una classe generica che gestisce l'iterazione per enumerazioni come le risposte di cui sopra.