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:
- ::std::to_underlying(value): Returns the underlying integer value. Include the
<utility>header. - static_cast<::std::underlying_type_t<T>>(value): Cast to the enum’s underlying type. Include the
<type_traits>header.
#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
- Scoped and strong:
enum classprevents name leakage and implicit conversions. - Underlying type: Specify one when storage size or ABI matters.
- Explicit integer conversion: Use
::std::to_underlyingorstatic_cast<::std::underlying_type_t<T>>; include<utility>or<type_traits>. - using enum: Bring values into scope to reduce verbosity; use judiciously.
- Arithmetic/bitwise: Operate on underlying integers; cast back to the enum for clarity.