7.7. Enumerated types

An enum (enumerated type) is a distinct type whose value is restricted to a range of values (see below for details), which may include several explicitly named constants (“enumerators”). The values of the constants are values of an integral type known as the underlying type of the enumeration.

There are two distinct kinds of enumerations:

unscoped enumeration

declared with the keyword enum

scoped enumeration

declared with the enum-key enum class or enum struct

7.7.1. Unscoped enumerations

Each enumerator becomes a named constant of the enumeration’s type (that is, name), visible in the enclosing scope, and can be used whenever constants are required.

enum color { red, green, blue };
color r = red;
switch(r)
{
  case red  : std::cout << "red\n";   break;
  case green: std::cout << "green\n"; break;
  case blue : std::cout << "blue\n";  break;
}

Each enumerator is associated with a value of the underlying type. When initializers are provided in the enumerator-list, the values of enumerators are defined by those initializers. If the first enumerator does not have an initializer, the associated value is zero. For any other enumerator whose definition does not have an initializer, the associated value is the value of the previous enumerator plus one.

Values of unscoped enumeration type are implicitly-convertible to integral types. If the underlying type is not fixed, the value is convertible to the first type from the following list able to hold their entire value range: int, unsigned int, long, unsigned long, long long, or unsigned long long. If the underlying type is fixed, the values can be converted to their promoted underlying type.

enum color { red, yellow, green = 20, blue };
color c = red; // c  implicitly converts to 0
int n = blue;  // n == 21

Consider the following program:

#include <iostream>
enum Direction { north, south, east, west };

void show_direction(int direction) {
  std::cout << "Direction: " << direction << '\n';
}

int main() {
  Direction dir = west;
  show_direction(dir);
  int num = dir;
  show_direction(num);

  for (int i = north; i < 8; ++i) {
    show_direction(i);
  }
  return 0;
}

Scoped enums were introduced to address these specific shortcomings.

7.7.2. Scoped enumerations

Like unscoped enumerations, each enumerator becomes a named constant of the enumeration’s type visible in the enclosing scope, and can be used whenever constants are required.

Unlike unscoped enums, scoped enums do not implicitly convert to integral types, but can be converted with a static or explicit cast.

enum class Color { red, green = 20, blue };
Color r = Color::blue;
switch(r)
{
  case Color::red  : std::cout << "red\n";   break;
  case Color::green: std::cout << "green\n"; break;
  case Color::blue : std::cout << "blue\n";  break;
}
// int n = r;                // error: no implicit int conversion
int n = static_cast<int>(r); // OK, n = 21

All the C++ enumerated types are very simple compared to the same types in other languages. However, it is easy to add features as needed. Given the following scoped enum:

// If this were declared enum struct, nothing in this example changes...
//
enum class Direction { NORTH, SOUTH, EAST, WEST };

We can implement a stream overload to allow printing enum members with human readable strings:

std::ostream& operator<<(std::ostream& os, const Direction& rhs) {
  std::string dir;
  switch (rhs) {
    case Direction::NORTH: dir = "NORTH"; break;
    case Direction::EAST:  dir = "EAST";  break;
    case Direction::SOUTH: dir = "SOUTH"; break;
    case Direction::WEST:  dir = "WEST";  break;
  }
  return os << dir;
}

And an array allows the enum to be used in a range for loop:

const std::array<Direction,4> directions =
{
  {
    Direction::NORTH,
    Direction::EAST,
    Direction::SOUTH,
    Direction::WEST
  }
};

Now the show directions program looks like this:

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

void show_direction(const Direction d) {
  std::cout << "Direction: " << d << std::endl;
}

int main() {
  Direction dir = Direction::WEST;
  std::cout << "Show one direction: " << std::endl;
  show_direction(dir);

  std::cout << "Loop through all directions: " << std::endl;
  for (const auto& d: directions) {
    show_direction(d);
  }
  return 0;
}

Another example uses the same techniques to build a deck of cards.

#include <array>
#include <iostream>
#include <vector>

enum class Rank {
  Ace = 1, Two, Three, Four, Five, Six, Seven, Eight, Nine, Ten, Jack, Queen, King
};

// Define an array of Rank to allow range-for looping
const std::array<Rank,13> ranks = 
{
  {
    Rank::Ace, Rank::Two, Rank::Three, Rank::Four, Rank::Five,    
    Rank::Six, Rank::Seven, Rank::Eight, Rank::Nine, Rank::Ten,    
    Rank::Jack, Rank::Queen, Rank::King     
  }
};

enum class Suit {
  Clubs, Diamonds, Hearts, Spades
};

// Define an array of Suit to allow range-for looping
const std::array<Suit,4> suits = 
{
  {
    Suit::Clubs, Suit::Diamonds, Suit::Hearts, Suit::Spades     
  }
};

struct Card {
  Rank rank;
  Suit suit;
};

std::ostream& operator<<(std::ostream& os, const Rank& rhs );
std::ostream& operator<<(std::ostream& os, const Suit& rhs );
std::ostream& operator<<(std::ostream& os, const Card& rhs );


// short, but take care . . . 
// a change in the order of the enum could break this,
// Assumes order: A,2,3,4,5,6,7,8,9,10,J,Q,K
// A more verbose switch would be better
std::ostream& operator<<(std::ostream& os, const Rank& rhs ) {
  if (rhs > Rank::Ace && rhs < Rank::Jack) {
    os << static_cast<int>(rhs);
  } else if (rhs == Rank::Ace) {
    os << "Ace";
  } else if (rhs == Rank::Jack) {
    os << "Jack";
  } else if (rhs == Rank::Queen) {
    os << "Queen";
  } else {
    os << "King";
  }

  return os;
}

std::ostream& operator<<(std::ostream& os, const Suit& rhs ) {
  switch(rhs) {
    case Suit::Clubs:    os << "Clubs"; break;
    case Suit::Diamonds: os << "Diamonds"; break;
    case Suit::Hearts:   os << "Hearts"; break;
    case Suit::Spades:   os << "Spades"; break;
  }
  return os;
}

std::ostream& operator<<(std::ostream& os, const Card& rhs ) {
  return os << rhs.rank << " of " << rhs.suit;
}

int main() {
  std::vector<Card> deck;
  for (const auto& s: suits) {
    for (const auto& r: ranks) {
      deck.push_back(Card{r,s});
    }
  }
  for(const auto& c : deck ) {
    std::cout << c << '\n';
  }
  return 0;
}

You have attempted of activities on this page