2011-12-14 8 views
47

Ho un blocco di codice ricorrente dove eseguo il loop su tutti i membri di un enum class.Allow for Range-Based For con classi enum?

Il ciclo for attualmente utilizzato sembra molto poco maneggevole rispetto al nuovo range-based for.

C'è un modo per sfruttare le nuove funzionalità di C++ 11 per ridurre la verbosità per il mio ciclo corrente for?

codice corrente che vorrei migliorare:

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

In altre parole, sarebbe bello se potessi fare qualcosa di simile:

for(const auto& c : COLOR) 
{ 
    // do work 
} 
+9

Interessante. Ada ha questa caratteristica dal 1983. – Shark8

+7

'(COLOR) (((int) (x) + 1))' Invece di 'int', considera l'uso di' std :: underlying_type :: type'. –

+5

È previsto che il viola venga saltato? – kennytm

risposta

25

enumerazioni iterazione con l'enumerazione in sé come un l'iteratore è una cattiva idea e consiglio di utilizzare un iteratore effettivo come nella risposta di deft_code. Ma se questo è davvero ciò che si vuole:

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() { 
    for(const auto& c : COLOR()) { //note I added parenthesis here to make an instance 
     //do work 
    } 
    return 0; 
} 

Lavorare qui: http://ideone.com/cyTGD8


Sul lato iteratore delle cose, il modo più semplice è semplicemente:

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 : COLOR()) { //note I added parenthesis here to make an instance 
     //do work 
    } 
    return 0; 
} 

Come si vede qui: http://ideone.com/9XadVt

(La dichiarazione e la definizione separate della matrice lo rendono un errore del compilatore se il numero di colori non corrisponde al numero di elementi nella matrice. controllo ETY)

+2

Richiede un 'operatore *' per rendere 'COLOR' un iteratore di input. –

+0

@ R.MartinhoFernandes come dovrebbe apparire la firma di questo 'operatore *'? – kfmfe04

+2

@ kfmfe04 L'ho aggiunto alla risposta. –

2

Ecco un esempio collaudato (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); //
    return 0; 
} 
+1

Funziona anche con Intel C++ 2013 SP1 su Linux, quando si installa Update 1 che non riesce a compilare, abbiamo segnalato un problema a Intel Engineering per questo. –

38

io personalmente non mi piace sovraccaricare l'operatore ++ per enumerazioni. Spesso incrementa un valore enum non ha molto senso. Tutto ciò che è veramente voluto è un modo per iterare sull'enumerazione.

Di seguito è una classe generica Enum che supporta l'iterazione. È funzionale ma incompleta. Una vera implementazione farebbe bene a limitare l'accesso al costruttore e aggiungere tutti i tratti iteratori.

#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; 
    } 
} 
+1

concordato. Questo è molto, molto meglio che sovraccaricare gli operatori sul tipo di enumerazione stesso. –

+0

+1 Nice - Ho modificato la mia classe Enum per seguire il tuo formato (non in OP). La sicurezza del tipo è carina, ma 'T from_string (const string & T)' è un po 'un problema come in C++, non possiamo sovraccaricare il valore di enum di ritorno. Lo stesso problema esiste indipendentemente dal fatto che io usi un modello-Enum, ma con template-Enum, è solo un po 'più prolisso. – kfmfe04

+6

[Implementazione completa di lavoro] (http://stacked-crooked.com/view?id=bcee5da83cd5c0738a17962bc00ee82d), notate anche che ho cambiato il valore di "Ultimo" per essere più coerente con i normali intervalli di iterazione. –

4

Si potrebbe probabilmente fare qualcosa di intelligente con boost :: MPL, una versione grezza potrebbe essere simile:

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

Mi piace l'idea molto e spesso desiderato per esso.

Il problema che vedo è che cosa succede quando c'è un valore numerico ripetuto per un oggetto enum. Tutte le implementazioni che vedo sopra richiedono cast per tipo integrale e ++. In definitiva, penso che il supporto linguistico potrebbe essere richiesto per iterare veramente su ciascun elemento in tutti i casi. Eliminerebbe la necessità di avere Primo, Ultimo o Inizio, Fine anche se non obietto troppo a questo. È come cercare begin() end() per i contenitori.

enum class COLOR 
{ 
    Blue, 
    Red, 
    Green, 
    Mauve = 0, 
    Purple, 
    Last 
}; 

La numerazione ricomincia da Mauve.

+0

Sì o quando ci sono spazi vuoti. –

28
enum class Color { 
    blue, 
    red, 
    green = 5, 
    purple 
}; 
const std::array<Color,4> all_colors = {Color::blue, Color::red, Color::green, Color::purple}; 

Poi:

for (Color c : all_colors) { 
    //... 
} 

Molte volte lo uso come questo, dove voglio un valore 'none':

// 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 
    // ... 
} 
+6

Questa è un'ottima risposta! La ridondanza di avere gli articoli in due punti è un peccato, ma è una soluzione molto più pulita rispetto alle altre soluzioni; questo non ha casting e, come dici tu, i valori non devono iniziare a zero o essere contigui. Puoi anche facilmente specificare sottoinsiemi di valori, ad es. darkColors e lightColors. Si basa un po 'su enumerazioni con un numero di valori piuttosto basso, ma di solito lo fanno comunque. –

+0

Un addendum alla tua risposta: con questa soluzione puoi ottenere il conteggio dei valori con colors.size(). Se hai bisogno di una costante di tempo di compilazione (per la dimensione di un altro array, per esempio) puoi usare std :: tuple_size (decltype (colors)) :: value, che è certamente un po 'prolisso ma totalmente sicuro. –

+0

Perché la dimensione dell'array è specificata come '3'? Non dovrebbe essere '2'? E se è così, questo non dimostra che la violazione del principio ASCIUTO porta sempre a bug difficili da vedere? –

0

Se sei una persona terribile si può ottenere questo comportamento con il preprocessore, qualcosa come:

#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 piccole modifiche questo potrebbe anche supportare ad es. ENUM_SETVALUE (Blu, 4) e creando una mappa const da es. COLORE: da blu a "blu". E viceversa.

Vorrei che lo standard avesse appena creato queste funzionalità come opzioni per enum class. Nessuno dei workaround è buono.

0

Sono sicuro che è possibile scorrere i membri di un C++ initializer_list, quindi mi sa che ho fatto in passato:

enum class Color {Red, Green, Blue}; 

for (const Color c : {Color::Red, Color::Green, Color::Blue}) 
{ 
} 

Se ci sono problemi con questo, non lo faccio lo so, ma ho pensato di suggerirlo perché è conciso, ma non è l'ideale se ci sono molti colori.