How to use enum class values ​​as part of for-loop?

I'm trying to create a deck of cards by iterating over the Suit and Rank listings (I know there is no great way to iterate over transitions, but I don't see an alternative). I did this by adding an enum_count enumerator to the end of each enum, whose value is intended to represent the length and end of the enumeration.

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

However, when I try to iterate over enum, the compiler does not like that I am trying to increase suit++ and rank++ in a for loop, specifying:

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

What is the best way to create a deck of cards without discarding useful enum data structures?

+6
source share
5 answers

I would recommend doing something else. Create a vector Suit from one to Rank , and scroll them using the power of 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}; 

Yes, you have to type them twice, but this allows you to use whatever values ​​you want for them (i.e., not sequentially), and not to use uncomfortable things like enum_count (Which card do you want? Give me enum_count diamonds !! ), there is no need for casting and use the iterators provided by std::vector .

To use them:

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

You can apply the variables suit and rank to int& and increase them as such.

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

However, this can cause some problems, for example, when you assign values ​​to your enum entries.


You can also create a small function that will do this for you with the correct type:

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

You cannot use this with enum class . You should use the old enum style.

If you insist on using them. I can offer you a bad decision not to use (unless you tell anyone what I suggested):

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

Additional answer from old_mountain answer:

In some cases, you can remember to add new values ​​to your list using fixed arrays. The main problem is that the initializer takes fewer arguments than indicated, but you can get around this:

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

I found make_array inspired in this question, but changed it: How to emulate C array initialization and int arr [] = {e1, e2, e3, ...} "behavior with std :: array?

What he does is use the first argument to determine the type of std::array and the number of arguments to get the actual size. Thus, the return type is std::array<firstType, numArgs> .

Now declare your lists as follows:

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

When you add a value to the array, your enum_count or any other value that you use as a separator will change, and therefore the assignment from make_array will fail, as the sizes of both std::array will be different (which leads to different types).

Example:

If you just add a new Suit , say hexa

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

Compiler Crash:

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

I have to admit that I am not 100% satisfied with this solution, as declaring an array requires a pretty ugly tide of size_t .

+2
source

I also want to share my approach based on the previous answer that creates map<enum,string> using the capabilities of C ++ 11 and C ++ 14, the code below:

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

Using this template, we can modify the Deck constructor as follows:

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

The only requirement to make it all work is to call the initialize function as follows:

 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" ); 

You can see the Live example.

+1
source

All Articles