How to use enum class values as part of for-loop?
How to use enum class values as part of for-loop?
I'm trying to create a deck of cards by iterating over the enums Suit
and Rank
(I know there's no great way to iterate over enums but I don't see an alternative). I did this by adding an enumerator enum_count
to the end of each enum, whose value is meant to represent the length and end of the enum.
#include 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 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 loop over the enum, the compiler doesn't like that I'm trying to increment the suit++
and rank++
in the for-loop, stating:
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 go about creating a deck of cards without throwing away the useful enum data structures?
Answer by Humam Helfawi for How to use enum class values as part of for-loop?
You can not use this with enum class
. You have to use the old style enum
.
If you insist you use them. I can suggest you a bad solution not to use (unless you won't tell anyone that I suggested it):
for (Rank rank = Rank::one; static_cast(rank) < static_cast(Rank::enum_count); rank = static_cast(static_cast(rank)+1)){ };
Answer by Simon Kraemer for How to use enum class values as part of for-loop?
You could cast your suit
and rank
variables to an 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)++) {
Yet this might cause some problems like when you assign values to your enum entries.
You could also create a little function that does this for you with the correct type:
template T& increment(T& value) { static_assert(std::is_integral>::value, "Can't increment value"); ((std::underlying_type_t&)value)++; return value; } Deck::Deck() { bool a = std::is_integral>::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); }; }; };
Answer by old_mountain for How to use enum class values as part of for-loop?
I would recommend doing something different. Create a vector of Suit
and one to Rank
, and loop over them using the power of STL
const std::vector v_suit {Suit::clubs, Suit::diamonds, Suit::hearts, Suit::spades}; const std::vector 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 permits you to use whatever values you want for them (ie. not consecutive), not use awkward stuff like enum_count
(What card do you want? Give me a diamonds enum_count!!), no need for casting, and use the iterators provided to std::vector
.
To use them:
for(const auto & s : v_suit) for (const auto & r : v_rank) cards.push_back({s,r});
Answer by Simon Kraemer for How to use enum class values as part of for-loop?
Additional answer in response to old_mountain's answer:
You can in some cases prevent that you forget to add new values to your list by using fixed arrays. The main problem with this is that the initializer accepts less arguments than specified but you can work around this:
template struct first_type { using type = T; }; template std::array::type, sizeof...(Args)> make_array(Args&&... refs) { return std::array::type, sizeof...(Args)>{ { std::forward(refs)... } }; }
I found the inspiration to make_array
in this question, but modified it: How to emulate C array initialization "int arr[] = { e1, e2, e3, ... }" behaviour with std::array?
What it does is to use the first argument to find out what type the std::array
shall be of and the number of arguments to get the real size. So the return type is std::array
.
Now declare your lists like this:
const std::array SuitValues = make_array(Suit::clubs, Suit::diamonds, Suit::hearts, Suit::spades); const std::array 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 whatever value you are using as delimiter will change and therefore the assignment from make_array
will fail as the sizes of both std::array
s differ (which results in different types).
Example:
If you just add a new Suit
, let's say hexa
enum class Suit : int { clubs, diamonds, hearts, spades, hexa, enum_count };
The compiler will fail with:
cannot convert from 'std::array' to 'const std::array'
I have to admit that I am not 100% happy with this solution as it requires a pretty ugly cast to size_t
in the array declaration.
Answer by PaperBirdMaster for How to use enum class values as part of for-loop?
I also want to share my approach, based on a previous answer of mine which creates map
using C++11 and C++14 features, the code is the one below:
// Shortcut to the map template using enum_map = std::map; // Template variable for each enumerated type template enum_map enum_values{}; // Empty function to end the initialize recursion void initialize(){} // Recursive template which initializes the enum map template void initialize(const ENUM value, const char *name, args ... tail) { enum_values.emplace(value, name); initialize(tail ...); }
With this template, we can change the Deck
constructor this way:
Deck::Deck() { for (const auto &S : enum_values) { for (const auto &R : enum_values) { Card created_card; created_card.suit = S.first; created_card.rank = R.first; cards.push_back(created_card); }; }; };
The only requirement in order to make the whole thing work is to call the initialize
function this way:
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 take a look at the Live example.
Fatal error: Call to a member function getElementsByTagName() on a non-object in D:\XAMPP INSTALLASTION\xampp\htdocs\endunpratama9i\www-stackoverflow-info-proses.php on line 72
0 comments:
Post a Comment