2016-02-10 12 views
6

che sto cercando di creare un mazzo di carte da scorrere l'enumerazioni Suit e Rank (so che non c'è ottimo modo per iterare enumerazioni, ma non vedo un'alternativa). Ho fatto questo aggiungendo un enumeratore enum_count alla fine di ogni enum, il cui valore è destinato a rappresentare la lunghezza e la fine della enum.Come utilizzare i valori della classe enum come parte del ciclo for?

#include <vector> 

using namespace std; 

enum class Suit: int {clubs, diamonds, hearts, spades, enum_count}; 
enum class Rank: int {one, two, three, four, five, six, seven, eight, 
       nine, ten, jack, queen, king, ace, enum_count}; 

struct Card { 
    Suit suit; 
    Rank rank; 
}; 

class Deck{ 
    vector<Card> cards{}; 
    public: 
     Deck(); 
}; 

Deck::Deck() { 
    // ERROR ON THE BELOW LINE 
    for (Suit suit = Suit::clubs; suit < Suit::enum_count; suit++) { 
     for (Rank rank = Rank::one; rank < Rank::enum_count; rank++) { 
      Card created_card; 
      created_card.suit = suit; 
      created_card.rank = rank; 
      cards.push_back(created_card); 
     }; 
    }; 
}; 

Tuttavia, quando provo a un loop all'interno di enum, il compilatore non piace che io sto cercando di incrementare il suit++ e rank++ nel ciclo for, affermando:

card.cpp|24|error: no ‘operator++(int)’ declared for postfix ‘++’ [-fpermissive]| 
card.cpp|25|error: no ‘operator++(int)’ declared for postfix ‘++’ [-fpermissive]| 

Qual è il modo migliore per creare un mazzo di carte senza buttare via le utili strutture di dati enum?

+0

[Relativo?] (Http://stackoverflow.com/questions/261963/how-can-i-iterate-over-an-enum) –

+1

@JamesRoot Ho provato a replicare molte delle soluzioni proposte in quella domanda (anche se la maggior parte delle risposte ci sono su enumerazioni inutilizzate) e altre che ho trovato su internet, ma senza risultato. Così ho postato qui il mio miglior tentativo. – DBedrenko

+1

@NewWorld Ho chiesto [qualcosa di simile] (http://stackoverflow.com/questions/13971544/using-enum-in-loops-and-value-consistency) qualche tempo fa. –

risposta

4

lo consiglio a fare qualcosa di diverso. Creare un vettore di Suit e uno a Rank, e un ciclo su di loro usando il potere della STL

const std::vector<Suit> v_suit {Suit::clubs, Suit::diamonds, Suit::hearts, Suit::spades}; 

const std::vector<Rank> v_rank {Rank::one, Rank::two, Rank::three, Rank::four, Rank::five, 
          Rank::six, Rank::seven, Rank::eight, Rank::nine, Rank::ten, Rank::jack, 
          Rank::queen, Rank::king, Rank::ace}; 

Sì, è necessario digitare due volte, ma questo consente di utilizzare qualunque valori desiderati per loro (cioè . non consecutivi), non usare roba scomodi come enum_count (Che scheda vuoi? Dammi diamanti enum_count !!), non è necessario per la fusione, e utilizzare le iteratori forniti ai std::vector.

Per utilizzarli:

for(const auto & s : v_suit) 
    for (const auto & r : v_rank) 
     cards.push_back({s,r}); 
+0

Grazie per l'aiuto :) Mi piace questa soluzione la migliore finora; è la più pulita, e l'unica parte brutta è che l'enum e il vettore devono essere mantenuti sincronizzati manualmente dal programmatore, se mai uno di loro cambia, ma penso che sia davvero accettabile. – DBedrenko

+0

Non sarebbe meglio rendere costanti i vettori? Voglio dire, nessuno dovrebbe mai modificarli in fase di runtime. –

+0

L'ho implementato in questo modo e funziona benissimo, ma posso sapere perché si passano i riferimenti ('&') nei loop for? Il mazzo è creato anche senza riferimenti. – DBedrenko

2

Non è possibile utilizzare questo con enum class. Devi usare il vecchio stile enum.

Se si insiste di utilizzarli. Posso suggerire una cattiva soluzione non usare (a meno che non lo dirai a nessuno che ho suggerito di esso):

for (Rank rank = Rank::one; 
    static_cast<int>(rank) < static_cast<int>(Rank::enum_count); 
    rank = static_cast<Rank>(static_cast<int>(rank)+1)){ 
}; 
+0

Grazie per la risposta, potresti dirmi perché l'uso di 'static_cast' come questo è" cattivo "? – DBedrenko

+0

non static_cast .. l'intera soluzione è fittizia. Faccio una domanda simile prima di sbarazzarmi di questo e non c'era molta scelta. Penso che dovresti riconsiderare l'intero design per trovare un modo migliore. –

+0

Hmm allora cosa c'è che non va nella soluzione? Sto cercando un design migliore, ma una classe Enum sembra il modo perfetto per modellare tute e gradi di carte ... – DBedrenko

3

potresti lanciare i tuoi suit e rank variabili da un int& e aumentare in quanto tale.

for (Suit suit = Suit::clubs; suit < Suit::enum_count; ((int&)suit)++) { 
     for (Rank rank = Rank::one; rank < Rank::enum_count; ((int&)rank)++) { 

Tuttavia, questo potrebbe causare alcuni problemi come quando si assegnano valori alle voci enum.


Si potrebbe anche creare una piccola funzione che fa questo per voi con il tipo corretto:

template <typename T> 
T& increment(T& value) 
{ 
    static_assert(std::is_integral<std::underlying_type_t<T>>::value, "Can't increment value"); 
    ((std::underlying_type_t<T>&)value)++; 
    return value; 
} 

Deck::Deck() { 
    bool a = std::is_integral<std::underlying_type_t<Suit>>::value; 

    // ERROR ON THE BELOW LINE 
    for (Suit suit = Suit::clubs; suit < Suit::enum_count; increment(suit)) { 
     for (Rank rank = Rank::one; rank < Rank::enum_count; increment(rank)) { 
      Card created_card; 
      created_card.suit = suit; 
      created_card.rank = rank; 
      cards.push_back(created_card); 
     }; 
    }; 
}; 
+0

Grazie per la soluzione interessante. Cosa fa '(int &)' fare qui esattamente? So che 'int & some_arg' può essere usato nelle definizioni dei parametri di funzione come pass-by-reference. – DBedrenko

+1

'int &' è un riferimento a una variabile di tipo 'int'. Quindi, quando cambi il riferimento, cambi la variabile valore i riferimenti di riferimento. '((int &) suit) ++' potrebbe anche essere scritto come 'int & temp = (int &)suit; temp ++;'. L'unico motivo per cui questo funziona è che 'Suit' e' Rank' "ereditano" int, quindi questo cast è sicuro –

+0

@Simon Rendilo generico e sei d'oro – LogicStuff

2

risposta aggiuntive in risposta a old_mountain's answer:

Si può, in alcuni casi evitare che si dimentica di aggiungere nuovi valori alla tua lista mediante matrici fisse.Il problema principale di questo è che l'inizializzatore accetta meno argomenti di quanto specificato, ma si può ovviare a questo:

template<typename T, typename...Args> 
struct first_type 
{ 
    using type = T; 
}; 

template<typename... Args> 
std::array<typename first_type<Args...>::type, sizeof...(Args)> make_array(Args&&... refs) 
{ 
    return std::array<typename first_type<Args...>::type, sizeof...(Args)>{ { std::forward<Args>(refs)... } }; 
} 

ho trovato l'ispirazione per make_array in questa domanda, ma modificato è: How to emulate C array initialization "int arr[] = { e1, e2, e3, ... }" behaviour with std::array?

Cosa fa è quello di utilizzare il primo argomento per scoprire quale tipo di std::array deve essere e il numero di argomenti per ottenere la dimensione reale. Quindi il tipo di ritorno è std::array<firstType, numArgs>.

Ora dichiarare le vostre liste in questo modo:

const std::array<Suit, (size_t)Suit::enum_count> SuitValues = 
    make_array(Suit::clubs, Suit::diamonds, Suit::hearts, Suit::spades); 

const std::array<Rank, (size_t)Rank::enum_count> RankValues = 
    make_array(Rank::one, Rank::two, Rank::three, Rank::four, 
       Rank::five, Rank::six, Rank::seven, Rank::eight, 
       Rank::nine, Rank::ten, Rank::jack, Rank::queen, 
       Rank::king, Rank::ace); 

Quando si aggiunge un valore alla matrice tua enum_count o qualsiasi valore che si sta utilizzando come delimitatore cambierà e quindi l'assegnazione da make_array fallirà come le dimensioni di entrambi std::array s differiscono (che si traduce in tipi diversi).

Esempio:

Se si aggiunge solo un nuovo Suit, diciamo hexa

enum class Suit : int { clubs, diamonds, hearts, spades, hexa, enum_count }; 

Il compilatore non riuscirà con:

cannot convert from 'std::array<T,0x04>' to 'const std::array<Suit,0x05>' 

Devo ammettere che non sono al 100% felice con questa soluzione in quanto richiede un cast piuttosto brutto a size_t nella dichiarazione dell'array.

1

voglio anche condividere il mio approccio, basato su un previous answer of mine che crea map<enum,string> utilizzando C++ 11 e C++ 14 caratteristiche, il codice è quello qui sotto:

// Shortcut to the map 
template <typename ENUM> 
using enum_map = std::map<ENUM, const std::string>; 

// Template variable for each enumerated type 
template <typename ENUM> 
enum_map<ENUM> enum_values{}; 

// Empty function to end the initialize recursion 
void initialize(){} 

// Recursive template which initializes the enum map 
template <typename ENUM, typename ... args> 
void initialize(const ENUM value, const char *name, args ... tail) 
{ 
    enum_values<ENUM>.emplace(value, name); 
    initialize(tail ...); 
} 

Con questo modello, abbiamo può cambiare il costruttore Deck in questo modo:

Deck::Deck() { 
    for (const auto &S : enum_values<Suit>) { 
     for (const auto &R : enum_values<Rank>) { 
      Card created_card; 
      created_card.suit = S.first; 
      created_card.rank = R.first; 
      cards.push_back(created_card); 
     }; 
    }; 
}; 

l'unico requisito per far funzionare il sistema è quello di chiamare la funzione initialize in questo modo:

initialize 
(
    Suit::clubs, "Clubs", 
    Suit::diamonds, "Diamonds", 
    Suit::hearts, "Hearts", 
    Suit::spades, "Spades", 

    Rank::one, "1", 
    Rank::two, "2", 
    Rank::three, "3", 
    Rank::four, "4", 
    Rank::five, "5", 
    Rank::six, "6", 
    Rank::seven, "7", 
    Rank::eight, "8", 
    Rank::nine, "9", 
    Rank::ten, "10", 
    Rank::jack, "J", 
    Rank::queen, "Q", 
    Rank::king, "K", 
    Rank::ace, "A" 
); 

Potete dare un'occhiata al Live example.

Problemi correlati