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 <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 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?
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<int>(rank) < static_cast<int>(Rank::enum_count);
rank = static_cast<Rank>(static_cast<int>(rank)+1)){
};
With C++11, you can use a range-based for loop. All you need is to define an iterator class with operator++
, operator!=
, and operator*
and to define begin
or end
as member functions or free functions.
Here is an example using an EnumRange
class that includes an Iterator
class and begin
or end
member functions. The class assumes that T
is an enum class
with contiguous values that start at 0 and end at MAX
. The MAX
declaration is used to avoid adding an invalid value, such as enum_count
, to the enum. If the enum class
does not define MAX
, then the code will not compile.
template <class T>
struct EnumRange {
struct Iterator {
explicit Iterator(int v) : value(v) {}
void operator++() { ++value; }
bool operator!=(Iterator rhs) { return value != rhs.value; }
T operator*() const { return static_cast<T>(value); }
int value = 0;
};
Iterator begin() const { return Iterator(0); }
Iterator end() const { return Iterator(static_cast<int>(T::MAX) + 1); }
};
This allows you to simply write:
enum class Suit {clubs, diamonds, hearts, spades, MAX=spades};
enum class Rank {one, two, three, four, five, six, seven, eight,
nine, ten, jack, queen, king, ace, MAX=ace};
for(const Suit s : EnumRange<Suit>())
for (const Rank r : EnumRange<Rank>())
cards.push_back({s,r});
An advantage of this approach is that it avoids the need to define/allocate a map or vector each time you want to iterator over an enum. Instead, the EnumRange::Iterator
class stores a single integer and any changes to the enum are automatically supported. Also, since we have defined operator*
to cast the integer to the enum type T
, we know that the variable type of the range-based for loop is the enum.
All together, the result is the easy-to-read expression for(MyEnum s : EnumRange<MyEnum>())
.
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<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 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});
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<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 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<firstType, numArgs>
.
Now declare your lists like this:
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 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<T,0x04>' to 'const std::array<Suit,0x05>'
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With