How C++ std::underlying_type Determines Enum Backing Types
In C++, std::underlying_type is a standard type trait
that extracts the underlying integral type of an enumeration at compile
time. This article explains the mechanism behind
std::underlying_type, detailing how the compiler
establishes the numeric backing type of scoped and unscoped enums, and
how standard library implementations leverage compiler intrinsics to
expose this type to developers.
The Role of Compiler Intrinsics
At the library level, std::underlying_type cannot deduce
an enumeration's backing type using pure template metaprogramming or
pattern matching. Because the mapping between an enum and its underlying
integer representation is resolved directly inside the compiler's
semantic analysis phase, the standard library delegates this work to a
compiler intrinsic.
In major toolchains like GCC, Clang, and MSVC, the implementation of
std::underlying_type resembles the following:
template <typename T>
struct underlying_type {
using type = __underlying_type(T);
};
template <typename T>
using underlying_type_t = typename underlying_type<T>::type;The intrinsic __underlying_type(T) queries the
compiler's internal Abstract Syntax Tree (AST) for the symbol definition
of T, verifies that T is a complete
enumeration type, and immediately returns the designated integer
type.
How the Compiler Establishes the Underlying Type
To provide the intrinsic with the correct type, the compiler assigns an underlying integral type during the declaration of the enumeration based on ISO C++ rules:
- Scoped Enumerations (
enum classandenum struct)- Explicit type: If specified (e.g.,
enum class Status : uint8_t), the underlying type is strictly that type. - Implicit default: If no type is specified (e.g.,
enum class Status), the standard dictates that the underlying type defaults directly toint.
- Explicit type: If specified (e.g.,
- Unscoped Enumerations with an Explicit Type
- If an unscoped enum specifies a type (e.g.,
enum Flags : unsigned long), the underlying type is fixed to that specification, matching the behavior of scoped enums.
- If an unscoped enum specifies a type (e.g.,
- Unscoped Enumerations without an Explicit Type
- When an unscoped enum omits an explicit base (e.g.,
enum Color { Red, Green, Blue }), the underlying type is not fixed until all enumerators are evaluated. - The compiler analyzes the smallest and largest values among the enumerators.
- It then selects an implementation-defined integral type capable of
representing all defined enumerator values. Typically, the compiler
checks types in an order such as
int,unsigned int,long,unsigned long,long long, orunsigned long long. - If the values fit within
intorunsigned int, one of those is chosen according to the signs of the values.
- When an unscoped enum omits an explicit base (e.g.,
Constraints and Validity
Before resolving the type, the compiler verifies that the template
argument satisfies the requirements of
std::underlying_type:
- Completeness: The enumeration must be fully
defined. Applying
std::underlying_typeto a forward-declared enum without a fixed underlying type results in a compile-time error. - Type Category: The type must be an enumeration. Passing non-enum types (such as classes, pointers, or primitive integers) produces a compilation failure within the intrinsic evaluation.