Ch2.16: enum class

What is an enum class

An enum class (scoped enumeration) defines a set of named constants that are strongly typed and scoped. Values do not leak into the surrounding scope, and there is no implicit conversion to integers or other enums.


enum class Color { Red, Green, Blue };

// Declaration and comparison without leaking names
Color c{Color::Red};
bool is_red{c == Color::Red};

Underlying type specification

You can specify the underlying storage type. If omitted, it defaults to int.


enum class Status : unsigned char { Ok, Warning, Error };

// Declaration using the underlying storage
Status s{Status::Error};

Converting to and from the underlying integer type

enum class does not implicitly convert to integers. Use explicit conversions:


#include <utility>      // ::std::to_underlying
#include <type_traits>  // ::std::underlying_type_t

enum class ErrorCode { None, Minor, Major };

// To integer
auto v1{::std::to_underlying(ErrorCode::Major)};
auto v2{static_cast<::std::underlying_type_t<ErrorCode>>(ErrorCode::Major)};

// Back to enum
ErrorCode e1{static_cast<ErrorCode>(v1)};
ErrorCode e2{static_cast<ErrorCode>(v2)};

bool ok1{e1 == ErrorCode::Major};
bool ok2{e2 == ErrorCode::Major};

Use these integer values for arithmetic, bitwise operations, storage, or interop. Cast back to the enum type when you need semantic clarity.

Using enum to bring values into scope

With using enum, you can bring all values of an enum into the current scope and use them without qualification.


enum class Color { Red, Green, Blue };

Color a{Color::Green};

// Bring all Color values into scope
using enum Color;

Color b{Blue};         // No need to write Color::Blue
bool is_green{a == Green};

Keep readability in mind. using enum reduces verbosity, but it also hides the enum name. Use it where it clearly improves clarity (e.g., local scopes or short switch-like logic).

Arithmetic and bitwise operations with enums

For arithmetic and bitwise operations, convert enum values to their underlying integer type. Define flags or masks explicitly, then compute with integers.


#include <utility>

enum class Permission : unsigned {
  Read    = 1u,
  Write   = 2u,
  Execute = 4u
};

using enum Permission; // bring Read, Write, Execute into scope

// Build a mask from enum values via underlying integers
unsigned mask{::std::to_underlying(Read) | ::std::to_underlying(Write)};

// Test a flag
bool has_write{(mask & ::std::to_underlying(Write)) != 0u};

// Add a flag
mask |= ::std::to_underlying(Execute);

// Remove a flag
mask &= ~::std::to_underlying(Read);

// Store and restore from an integer
Permission p_exec{static_cast<Permission>(::std::to_underlying(Execute))};
bool is_exec{p_exec == Execute};

Avoid performing arithmetic directly on enum values. Always operate on the underlying integer and cast back to the enum where appropriate.

Key takeaways