Programming in C++

Enums and Scoped Enums

Gerald Senarclens de Grancy

What is an enum?

An enum is a type for limiting variables to be one of a set of named constants

  • Values are called elements, members, or enumerators of the type
  • Enumerators are identifiers that behave as constants
enum Name {
  first_choice = <integral constant>,  // assignment is optional; default is 0
  second_choice,  // value is previous value + 1
  ...
  nth_choice,
};
Name variable = second_choice;

The enum keyword may be used for declaring variables of an enum type

enum Name variable{second_choice};  // `enum` is optional
Name variable{second_choice};  // same as above

Purpose of Enums

Improve Readability

Enums can replace "magic numbers" with meaningful names

Example: Without Enums

// ... using magic constants





// imagine a network request
int status = get(url, buffer);
if (status == 200) {  // What is 200??
  // ...
} else if (status == 404) {  // 404??
  // ...
}

Example: Using Enums

// ...
enum Status {
  SUCCESS = 200,
  NOT_FOUND = 404
};

// imagine a network request
Status status = get(url, buffer);
if (status == SUCCESS) {
  // ...
} else if (status == NOT_FOUND) {
  // ...
}

Purpose of Enums

Automatically Create "Unique" Values

Values underlying an enum are automatically created if not assigned

enum Orientation {
  NORTH,  // 0
  EAST,  // 1
  SOUTH,
  WEST,
};

Better Maintainability

Changing values of these constants is done in a single location

Which Problems can Enums Cause?

No Scoping

Enum constants are not scoped by their enclosing enum block

enum Orientation { NORTH, EAST, SOUTH, WEST, };
// ...
const uint32_t NORTH = 0;  // compiler error: namespace is "polluted"

Misleading Implicit Conversion to int

Orientation direction = EAST;  // intention is clear
int choice = EAST;  // much harder to understand intention later on

No Default Underlying Type

Lack of regular forward declaration may require major re-compilation when adding a single enum constant

Forward declaration can be achieved since C++11 by specifying the underlying type

enum Orientation;  // ISO C++ forbids forward references to 'enum' types
enum Orientation: uint32_t;  // forward declaration with underlying type is OK

Compilers Allow Reusing Values

#include <iostream>
using std::cout, std::endl;

enum Color {
  RED = 5,
  GREEN,
  BLUE = 5,  // outch
};

int main() {
  if (RED == BLUE) {  // valid (an no compiler warning about this)
    cout << "what a mess" << endl;
  }
  return 0;
}

Visualize execution

Comparison of Unrelated enum Constants

#include <iostream>
using std::cout, std::endl;

enum Color { RED, GREEN, BLUE, };
enum Orientation { NORTH, EAST, SOUTH, WEST, };

int main() {
  if (RED == NORTH) {  // valid (at least, decent compilers warn about this)
    cout << "what a mess" << endl;
  }
  return 0;
}

Visualize execution

Example: Working With Enums

It is common to use switch when working with an enum

#include <iostream>
using std::cout, std::endl;

enum Orientation {
  NORTH,
  EAST,
  SOUTH,
  WEST,
};

int main() {
  Orientation heading = EAST;  // user controls movement of character
  switch (heading) {
  case NORTH:
    cout << "Hero moves north...\n";
    break;
  case EAST:
    cout << "Hero moves east...\n";
    break;
  case SOUTH:
    cout << "Hero moves south...\n";
    break;
  case WEST:
    cout << "Hero moves west...\n";
    break;
  }
  return 0;
}

Visualize execution

C++ Scoped Enums

Scoped enums (since C++11) address many of the prior limitations

  • Cannot be converted to integers implicitly
  • May always be forward declared (default underlying type is int)
  • Offer stronger type safety
  • Compiler prohibits comparison of unrelated constants
enum class Name {
  first_choice = <integral constant>,  // assignment is optional; default is 0
  second_choice,  // value is previous value + 1
  ...
  nth_choice,
};
Name variable = Name::second_choice;

The same value can still be assigned to different constants within a scoped enum

Example: Working With Scoped Enums

operator<< cannot be directly used without overloading

#include <iostream>
#include <utility>
using std::cout, std::endl;

enum class Orientation {
  NORTH,
  EAST,
  SOUTH,
  WEST,
};

std::ostream& operator<<(std::ostream& os, Orientation o) {
  switch (o) {
  case Orientation::NORTH:
    os << "north";
    break;
  case Orientation::EAST:
    os << "east";
    break;
  case Orientation::SOUTH:
    os << "south";
    break;
  case Orientation::WEST:
    os << "west";
    break;
  }
  return os;
}

int main() {
  Orientation o = Orientation::EAST;  // user controls movement of character
  std::cout << "The hero faces towards " << o << "." << std::endl;
  std::cout << std::to_underlying(o) << std::endl;  // since C++23
  return 0;
}
Download scoped_enum.cpp

Questions
and feedback...