C++ Type Traits for Compile-Time Introspection
The <type_traits> header in C++ provides a
comprehensive suite of compile-time templates used to query, inspect,
and evaluate the characteristics of types at compile time. Introduced in
C++11 and expanded in subsequent standards (C++14, C++17, and C++20),
these traits allow developers to inspect fundamental categories, examine
structural properties, and evaluate relationships between types. This
article breaks down the standard introspection traits into functional
categories, highlighting how they enable metaprogramming, template
constraints, and conditional compilation.
Fundamentals of Introspection Traits
Type introspection traits inherit from
std::integral_constant<bool, Value>, meaning they
expose a boolean compile-time constant member named value.
Since C++17, all boolean type traits feature an inline variable template
alias ending in _v (e.g.,
std::is_integral_v<T> instead of
std::is_integral<T>::value), simplifying usage inside
templates and static_assert statements.
Primary Type Categories
Primary type categories verify the fundamental nature of a type. Every C++ type belongs to exactly one primary category:
std::is_void<T>: Checks ifTisvoid.std::is_null_pointer<T>: Checks ifTisstd::nullptr_t(C++14).std::is_integral<T>: Checks ifTis an integral type (e.g.,bool,char,int,long long).std::is_floating_point<T>: Checks ifTis a floating-point type (float,double,long double).std::is_array<T>: Checks ifTis an array type of known or unknown bound (e.g.,int[]orint[5]).std::is_pointer<T>: Checks ifTis a raw pointer (excludes member pointers).std::is_lvalue_reference<T>: Checks ifTis an lvalue reference (U&).std::is_rvalue_reference<T>: Checks ifTis an rvalue reference (U&&).std::is_member_object_pointer<T>: Checks ifTpoints to a non-static data member.std::is_member_function_pointer<T>: Checks ifTpoints to a non-static member function.std::is_enum<T>: Checks ifTis an enumeration type (enumorenum class).std::is_union<T>: Checks ifTis a union type.std::is_class<T>: Checks ifTis a non-union class or struct.std::is_function<T>: Checks ifTis a function type.
Composite Type Categories
Composite categories are logical combinations of one or more primary categories:
std::is_reference<T>: Checks ifTis either an lvalue or rvalue reference.std::is_arithmetic<T>: Checks ifTis either an integral or floating-point type.std::is_fundamental<T>: Checks ifTis void, arithmetic, ornullptr_t.std::is_compound<T>: Checks ifTis non-fundamental (arrays, functions, pointers, references, classes, unions, enums).std::is_member_pointer<T>: Checks ifTis a pointer to a member object or member function.std::is_scalar<T>: Checks ifTis arithmetic, enum, pointer, member pointer, ornullptr_t.std::is_object<T>: Checks ifTis not a function, reference, or void type.
Type Properties and Modifiers
These traits inspect the qualities, qualifiers, and memory characteristics of types:
Const-Volatile Qualifiers and Sign
std::is_const<T>: Checks ifTisconst-qualified.std::is_volatile<T>: Checks ifTisvolatile-qualified.std::is_signed<T>: Checks ifTis a signed arithmetic type.std::is_unsigned<T>: Checks ifTis an unsigned arithmetic type.
Class Layout and Characteristics
std::is_empty<T>: Checks ifThas no non-static data members (except zero-width bitfields) and no virtual functions.std::is_polymorphic<T>: Checks ifTdefines or inherits at least one virtual function.std::is_abstract<T>: Checks ifTcontains at least one pure virtual function.std::is_final<T>: Checks ifTis declared with thefinalspecifier (C++14).std::is_aggregate<T>: Checks ifTis an aggregate type (C++17).std::is_standard_layout<T>: Checks ifTconforms to standard-layout rules for C-style interop.std::is_trivial<T>: Checks ifTis trivially copyable and has one or more trivial default constructors.
Supported Operations and Construction
The standard library provides inspection traits to verify how types can be constructed, assigned, and destroyed:
std::is_constructible<T, Args...>: Checks ifTcan be constructed with the argument typesArgs....std::is_default_constructible<T>: Checks ifThas an accessible default constructor.std::is_copy_constructible<T>: Checks ifTcan be copy-constructed.std::is_move_constructible<T>: Checks ifTcan be move-constructed.std::is_assignable<T, U>: Checks if a typeUcan be assigned toT.std::is_copy_assignable<T>: Checks ifTcan be copy-assigned.std::is_move_assignable<T>: Checks ifTcan be move-assigned.std::is_destructible<T>: Checks ifThas an accessible, non-deleted destructor.
Variants of these traits exist with prefixes to enforce stricter safety guarantees:
std::is_trivially_*: Verifies that the operation requires no compiler-generated logic (e.g., standard bitwise copy operations).std::is_nothrow_*: Verifies that the operation is guaranteed not to throw exceptions (noexcept).
Type Relationships and Invocability
These traits inspect the compatibility and relationship between two or more types:
std::is_same<T, U>: Checks ifTandUare identical, takingconst/volatilequalifiers into account.std::is_base_of<Base, Derived>: Checks ifBaseis a base class ofDerived(or the same class).std::is_convertible<From, To>: Checks if a typeFromcan be implicitly converted toTo.std::is_invocable<Fn, Args...>: Checks if a callable objectFncan be invoked with the argumentsArgs...(C++17).std::is_invocable_r<R, Fn, Args...>: Checks ifFncan be invoked withArgs...and yields a result convertible toR(C++17).
Practical Application
These introspection traits are predominantly used in conjunction with
static_assert for compile-time validation,
std::enable_if (or C++20 requires clauses) for
template overload resolution, and if constexpr statements
for branch pruning:
template <typename T>
void process_data(T value) {
if constexpr (std::is_pointer_v<T>) {
// Branch compiled only if T is a pointer
} else if constexpr (std::is_integral_v<T>) {
// Branch compiled only if T is an integer
} else {
static_assert(std::is_class_v<T>, "T must be a class, pointer, or integral type");
}
}