13.1. Enumerated types¶
In the previous chapter I talked about mappings between real-world values like rank and suit, and internal representations like integers and strings. Although we created a mapping between ranks and integers, and between suits and integers, I pointed out that the mapping itself does not appear as part of the program.
Actually, C++ provides a feature called an enumerated type that
makes it possible to (1) include a mapping as part of the program, and
(2) define the set of values that make up the mapping. For example, here
is the definition of the enumerated types card_suit and card_rank:
enum card_suit { clubs, diamonds, hearts, spades };
enum card_rank { ace = 1, two, three, four, five, six, seven, eight, nine,
ten, jack, queen, king };
Note
By default, the first value in the enumerated type maps to 0, the second to 1, and so on.
Within the card_suit type, the value clubs is represented by the integer
0, diamonds is represented by 1, etc.
The definition of card_rank overrides the default mapping and specifies
that ace should be represented by the integer 1. The other values
follow in the usual way.
Once we have defined these types, we can use them anywhere. For example,
the instance variables rank and suit can be declared with
type card_rank and card_suit:
struct playing_card {
card_rank rank;
card_suit suit;
playing_card (card_suit s, card_rank r);
};
The types of the parameters for the constructor have changed, too. Now, to create a card, we can use the values from the enumerated type as arguments:
playing_card card (diamonds, jack);
We use snake_case for enumerator names, just as for other identifiers. Named values communicate more than bare integers. The following call worked with the previous integer-based constructor, but does not compile with the new enumerated parameter types:
playing_card card (1, 11);
The active code below uses the enumerated types created above to construct playing_card objects.
Feel free to modify the values that the cards are being initialized to in the constructor: this will
change the output from the print function. Notice how this is much clearer than using integers.
1#include <iostream>
2#include <string>
3#include <vector>
4
5enum card_suit { clubs, diamonds, hearts, spades };
6
7enum card_rank { ace=1, two, three, four, five, six, seven, eight, nine,
8ten, jack, queen, king };
9
10struct playing_card {
11 card_rank rank;
12 card_suit suit;
13 playing_card (card_suit s, card_rank r);
14 void print () const;
15};
16
17int main() {
18 playing_card card1 (diamonds, jack);
19 card1.print ();
20 playing_card card2 (hearts, queen);
21 card2.print ();
22 playing_card card3 (clubs, three);
23 card3.print ();
24}
Because we know that the values in the enumerated types are represented
as integers, we can use them as indices for a vector. Therefore the old
print function will work without modification. We have to make some
changes in build_deck, though:
std::size_t index = 0;
for (int suit = clubs; suit <= spades; ++suit) {
for (int rank = ace; rank <= king; ++rank) {
deck[index].suit = static_cast<card_suit>(suit);
deck[index].rank = static_cast<card_rank>(rank);
index++;
}
}
The loop counters are integers. Each value is checked against the last
valid enumerator before it is converted with static_cast. This avoids
creating an out-of-range enum when the loop advances past the final suit.
An unscoped enum can convert to an integer in an expression such as
suit + 1, but ++ is not defined for it automatically.
Multiple Response: What can we do with enumerated types?
Assume we have the following struct defined by this enumerated type. What will be printed by the print function?
enum Scoops { SINGLE = 1, DOUBLE, TRIPLE };
enum Flavor { VANILLA, CHOCOLATE, STRAWBERRY, COOKIESNCREAM, MINTCHIP, COOKIEDOUGH };
enum Order { CUP, CAKECONE, SUGARCONE, WAFFLECONE }
struct iceCream {
Scoops scoops;
Flavor flavor;
Order order;
iceCream (Scoops s, Flavor f, Order o);
printOrder () {
// To save space, I didn't include the mapping. I'm sure you can still figure it out.
cout << "Who ordered a " << scoops[scoop] << " scoop of " << flavors[flavor] << " in a " << orders[order] << ?;
}
};
int main () {
iceCream icecream (2, 3, 2);
iceCream.printOrder();
}
Based on the card_rank enumerated type, what integer value does queen have?
13.1.1. Scoped enumerations¶
C++20 also supports scoped enumerations, introduced in C++11. For example:
enum class suit { clubs, diamonds, hearts, spades };
suit chosen = suit::hearts;
The qualified name prevents collisions with other names, and a scoped enum
does not implicitly convert to an integer. Use static_cast when an integer
representation is actually needed. The card examples in this chapter retain
unscoped enums to demonstrate their rules; both forms are valid C++20.