0

I would like to generate a deck of card objects in a standard, 52 card deck of playing cards that contain information about each card's suit and rank using C++.

The way I have been doing this so far is creating enumerated types for both the "Rank" and "Suit" information. So:

enum Rank {Ace, Two, Three, Four, Five, Six, Seven, Eight, Nine, Ten, Jack, Queen, King};
enum Suit {Heart, Spades, Diamonds, Clubs};

Then, I define a class 'Card' like this: `

class Card {

public: 

Rank CardRank;

Suit CardSuit;

};

` Now I need to generate an exhaustive list of all card objects using the two enumerated types (which I was going to do with some kind of "Generate Deck" function"). This sounds like it's going to involve some kind of for loop.

I tried to adapt the answer here to my situation, which looked like:

for ( int i = Ace; i != King; i++ )
{
    Card DummyCard;
    DummyCard.CardRank = static_cast<Rank>(i);
    std::cout << "This is " << DummyCard.CardRank << std::endl; //This line is just to     check what the program is doing

};

I'm having two problems:

  1. The program just counts the int values, rather than returning what I want (which is the rank values).

  2. That answer was dealing only with iterating over one a single enum and didn't deal with two enum types that are part of a class. I imagine I would need a nested for loop to iterate over the suits (in addition to the ranks), but I'm not really sure what that would look like.

So how can I iterate over two enum types that are both part of a class in order to generate an exhaustive list of class objects?

Konrad Rudolph
  • 530,221
  • 131
  • 937
  • 1,214
James G.
  • 1
  • 2
  • 1. is unclear. 2. yes you need two nested loops. – 463035818_is_not_an_ai Nov 28 '22 at 14:20
  • for 1. the code seems to be just fine. What do you mean with "The program just counts the int values, rather than returning what I want " ? Isnt `DummyCard.CardRank = static_cast(i);` exactly what you want ? (well the loops end is off by one but thats a minor) – 463035818_is_not_an_ai Nov 28 '22 at 14:22
  • C++ enums just don't have to built-in mechanisms to do what you want. So you have to roll your own `++` and `<<` operators for `Rank`. I could write a whole book just on enums. – Spencer Nov 28 '22 at 14:29
  • For #1, do you mean you aren't seeing the enumerator names in the output? – Spencer Nov 28 '22 at 14:36

3 Answers3

1

You cannot print out the "name" of enum items unless you use something like a std::map<Rank, std::string> and std::map<Suit, std::string>. Keep in mind that the terms "Ace", "Spades", etc. are in the same boat as variable names, they don't exist in the final executable. An enumeration is, in your case, a fancy way of mapping a more memorable name to (usually) an int value. So, when you output that enum, you're printing the underlying int, not the identifier (again, which doesn't exist at runtime).

As for the nested loop, you just need to do exactly what you did for the ranks. I'm not sure where the confusion is there. Also, keep in mind that if you say i != King, your loop will run for Ace through Queen. However, because the underlying type is an integer, you can just treat it like one. Ace is 0, King is 12, you can just say i <= King.

for (int r = Ace; r <= King; ++r) {
    for (int s = Heart; s <= Clubs; ++s) {
        Card DummyCard;
        DummyCard.CardRank = static_cast<Rank>(r);
        DummyCard.CardSuit = static_cast<Suit>(s);
        std::cout << r << " of " << s << std::endl;
    }
}
Skeptx
  • 63
  • 5
0

Yes you need another loop:

#include <iostream>
#include <vector>

enum Rank {Ace, Two, Three, Four, Five, Six, Seven, Eight, Nine, Ten, Jack, Queen, King};
enum Suit {Heart, Spades, Diamonds, Clubs};



struct Card { 
    Rank CardRank;
    Suit CardSuit;
};

int main() {
    std::vector<Card> cards;
    for (int rank = 0; rank < King+1; ++rank) { 
        for (int suit = 0; suit < Clubs+1; ++ suit) {
            cards.push_back({static_cast<Rank>(rank),static_cast<Suit>(suit)});
        }
    }

    for (const auto& card : cards) { 
        std::cout << card.CardRank << " " << card.CardSuit <<"\n";
    } 
 
}

However, it is unclear what you mean with "The program just counts the int values, rather than returning what I want" because static_cast<Rank>(i) is the Rank you get by converting the int to a Rank. It is not an int.

Your code looks fine, it only misses the last element because the loop only continues as long as i != King. To include also Kings you can change the condition to i < King+1;.

463035818_is_not_an_ai
  • 109,796
  • 11
  • 89
  • 185
  • I'm pretty sure OP means that the stream inserter is outputting each Rank value's integer value instead of its name. That requires a custom stream inserter. – Spencer Nov 28 '22 at 14:30
  • @Spencer I am pretty sure the question is unclear and OP should add clarification. I can delete this answer if it turns out to be irrelevant, but I hope it helps to get feedback from OP – 463035818_is_not_an_ai Nov 28 '22 at 14:31
  • This thing with enumerator names is always a gotcha for beginners. I was flabbergasted by this lack back when I started writing code in C++ instead of Pascal. – Spencer Nov 28 '22 at 14:39
  • @Spencer yeah well, but then I wonder why they ask about generating instances in a nested loop. If that was the issue they would have noticed after printing a single enum value. Perhaps you are right, but as often... only OP knows – 463035818_is_not_an_ai Nov 28 '22 at 14:41
  • To clarify: the output of the program is "This is 1" "This is 2" "This is 3", etc. – James G. Nov 29 '22 at 14:27
  • @JamesG. and this is not what you expected? If thats the issue then the quesiton is a bit misleading, because this has nothing to do with generating objects with values of all combinatations of the two enums – 463035818_is_not_an_ai Nov 29 '22 at 14:29
  • @JamesG. it always helps to include expected and actual output in the question – 463035818_is_not_an_ai Nov 29 '22 at 14:29
  • @463035818_is_not_a_number Sorry, I'm new at this. Yes, I was expecting the output "This is Ace", "This is Two", etc. Rather than "This is 1." – James G. Dec 01 '22 at 15:05
0

There is no native method to do this, but, it can be done.

Disclaimer: just because it can be done, doesn't mean you should, other than a fun exercise. Meaning, there is no reason to this in actual production code. If, for some reason, you need to, the best way is to use string maps or vectors, or, the classical hardcoded switch-cases where the name is returned based on the enum value. While this is not an automatic way and maintenance is always required, at this point in time, this is the way it can be done.

If you're looking for some "automatic" way, I can offer you two methods. A third-party and mine.

One way, if you don't mind using a third party (it's just a header, so you won't have linker worries) is using Daniil Goncharov's MagicEnum. It's using some smart introspection tricks using internal compiler representations of mangled names. Everything is done at compile time, so there should be no overhead (and no actual "code" to run everytime, it will already be written after compilation.

If, however, you don't want the hassle, there is a not so nice way using macro magic (not encouraged) and template magic. In order to conceal the "ugliness" you can move the magical part in a separate header.

You use IMPLEMENT_ENUM_STRING to declare an enum, having the first parameter as the enumeration name, and any following parameters as enumerators.

For example: IMPLEMENT_ENUM_STRING(Suit, Hearts, Spades, Diamonds, Clubs);

Then to display the name of the enumerator you use EnumToString(e), where e is a variable pointing to an enum declaration. Similarly to MagicEnum, all the work is done at compile-time since it's using static referencing and templates.

Also note I am using enum classes, also known as scoped enums, not old-style enums. And you should too. The difference is that they are type-safe and have better integral implementation. The downside is they are not backwards compatible with legacy code as they need to be scoped and can't be used as synonyms for integral PODs. (meaning you can't use them as ints or bitflags).

Furthermore, because enum classes aren't integrals, you can't iterate over them as you would over numbered sequences and enum classes don't have a native iterator (yet). In order to compensate for this I implemented an iterator similar in syntax to what std uses with begin/end for limits and ++ for iteration. To access a scoped enum, you'll need the scope operator (hence the name) ::. For example Suit::Hearts.

Also note that both, the iteration, as well as string conversion will work on any enum class defined using IMPLEMENT_ENUM_STRING, not just Rank and Suit. If you'll need more than 254 enumerators, you'll need to change the integral type of the enum class from uint8_t to something larger, such as uint32_t, as well as in the EnumToString<> template specialization.

There are, of course, ways to use scoped enums as integral PODs and implement bitflags, it does require a bit more code, but it also makes them C++standard-compliant. Anyways, back to business. Here is the source + usage (tested in MSVS and Code::Blocks using GCC):

EnumString.h

#pragma once
#include <regex>
#include <string>
#include <cstdint>
#include <vector>
#include <cstddef>

#define IDENTIFIER_TO_STRING(d_Identifier) #d_Identifier

#define IMPLEMENT_ENUM_STRING(d_Enum, ...)                                                                                                  \
    static const std::vector<std::string> Enum##d_Enum##Array = GetStringArrayFromEnumerators<nullptr>(#__VA_ARGS__);                       \
                                                                                                                                            \
    enum class d_Enum : uint8_t {__VA_ARGS__, end, begin = 0};                                                                              \
                                                                                                                                            \
    constexpr d_Enum& operator ++ (d_Enum& e)                                                                                               \
    {                                                                                                                                       \
        e = static_cast<d_Enum>(static_cast<uint8_t>(e) + 1);                                                                               \
        return e;                                                                                                                           \
    }                                                                                                                                       \
                                                                                                                                            \
    template <>                                                                                                                             \
    std::string EnumToString<d_Enum> (d_Enum e)                                                                                             \
    {                                                                                                                                       \
        auto enumerator = Enum##d_Enum##Array[static_cast<uint8_t>(e)];                                                                     \
                                                                                                                                            \
        return enumerator;                                                                                                                  \
    }

template <typename Enum>
std::string EnumToString (Enum e)
{
    return nullptr;
}

template <std::nullptr_t>
std::vector<std::string> GetStringArrayFromEnumerators (std::string String)
{
    std::regex word_regex("[, ]+");
    std::sregex_token_iterator  words_begin(String.begin(), String.end(), word_regex, -1);
    std::sregex_token_iterator  words_end;
    std::vector<std::string> word_list(words_begin, words_end);

    return word_list;
}

EnumString.cpp

#include <iostream>
#include "EnumString.h"

IMPLEMENT_ENUM_STRING(Suit, Hearts, Spades, Diamonds, Clubs);
IMPLEMENT_ENUM_STRING(Rank, Ace, Two, Three, Four, Five, Six, Seven, Eight, Nine, Ten, Jack, Queen, King);

int main (void)
{
    for (Rank rank = Rank::begin; rank != Rank::end; ++ rank)
    {
        for (Suit suit = Suit::begin; suit != Suit::end; ++ suit)
        {
            std::cout << EnumToString(rank) << " of " << EnumToString(suit) << std::endl;
        }
    }

    return 0;
}

This will output:

Ace of Hearts
Ace of Spades
Ace of Diamonds
Ace of Clubs
Two of Hearts
Two of Spades
Two of Diamonds
Two of Clubs
Three of Hearts
Three of Spades
Three of Diamonds
Three of Clubs
Four of Hearts
Four of Spades
Four of Diamonds
Four of Clubs
Five of Hearts
Five of Spades
Five of Diamonds
Five of Clubs
Six of Hearts
Six of Spades
Six of Diamonds
Six of Clubs
Seven of Hearts
Seven of Spades
Seven of Diamonds
Seven of Clubs
Eight of Hearts
Eight of Spades
Eight of Diamonds
Eight of Clubs
Nine of Hearts
Nine of Spades
Nine of Diamonds
Nine of Clubs
Ten of Hearts
Ten of Spades
Ten of Diamonds
Ten of Clubs
Jack of Hearts
Jack of Spades
Jack of Diamonds
Jack of Clubs
Queen of Hearts
Queen of Spades
Queen of Diamonds
Queen of Clubs
King of Hearts
King of Spades
King of Diamonds
King of Clubs
TheNomad
  • 892
  • 5
  • 6