Skip to main content
A scoped enumeration (introduced in C++11 via enum class or enum struct) is a distinct user-defined type consisting of a set of named integral constants, known as enumerators. Unlike traditional unscoped enumerations (enum), scoped enumerations are strongly typed and strongly scoped, preventing implicit type conversions and avoiding namespace pollution by confining the enumerators strictly to the enumeration’s scope.

Syntax

  • enum class and enum struct are semantically identical.
  • underlying_type is optional. If omitted, the compiler defaults to int. It can be any integral type (e.g., unsigned char, std::int32_t, short).

Core Mechanics

1. Strong Scoping

Enumerators defined inside a scoped enumeration are not exported to the enclosing scope. They must be accessed using the scope resolution operator (::). To alleviate verbosity, C++20 introduced the using enum declaration, which explicitly brings scoped enumerators into the local scope.

2. Strong Typing and Initialization

Scoped enumerations do not implicitly convert to their underlying integral type, nor do they implicitly convert to boolean or other enumeration types. Since C++17, a scoped enumeration can be initialized directly from an integer using direct-list-initialization, provided the value does not require a narrowing conversion.

3. Bitwise Operations Limitation

Because scoped enumerations strictly prohibit implicit conversions to their underlying integral types, standard bitwise operations (|, &, ^, ~) are not implicitly defined. Attempting to combine bitmask-style enumerators will result in a compiler error. To use a scoped enumeration as a bitmask, developers must explicitly overload the required bitwise operators.

4. Extracting the Underlying Value

Because implicit conversions are disabled, extracting the integer value requires explicit action. Historically, this required a static_cast. C++23 introduced std::to_underlying in the <utility> header as the standard, idiomatic way to extract the underlying value.

5. Explicit Underlying Types

You can explicitly define the memory footprint of the enumeration by specifying the underlying integral type. This guarantees the exact size of the type, which is critical for binary compatibility, serialization, or memory-constrained environments.

6. Forward Declarations

Because scoped enumerations have a deterministic underlying type (either explicitly declared or defaulting to int), the compiler knows their exact size. This allows scoped enumerations to be forward-declared, reducing compilation dependencies in header files.
Tired of Poor C++ Skills? Fix That With Deep Grasping!Learn More