C++ Template Metaprogramming and Type Traits

Last update: 2021-05-24 02:08:49 +0800 +0800
Available translations: 日本語 / 中文(简体) / 中文(繁體)

Template metaprogramming selects types and values during translation. The standard type-traits library supplies common building blocks; constexpr, if constexpr, and concepts express many decisions more directly than recursive class-template specializations.

Traits, Transforms, and Predicates #

Traits conventionally expose a nested type or a value. Standard aliases ending in _t extract a type, while variable templates ending in _v extract a Boolean or other value.

cpp
#include <concepts>
#include <type_traits>

template <class T>
using storage_type = std::remove_cvref_t<T>;

template <class T>
inline constexpr bool is_plain_object_v =
    std::same_as<T, std::remove_cvref_t<T>>;

Use transforms to normalize interfaces deliberately:

  • std::remove_cv_t, std::remove_reference_t, and std::remove_cvref_t remove qualifications for type comparison.
  • std::decay_t additionally performs array-to-pointer and function-to-pointer adjustments, matching by-value argument passing.
  • std::conditional_t chooses one of two types without defining separate specializations.
  • std::common_type_t computes a common type under its specified conversion rules; it is not a replacement for domain-specific unit or precision policy.

Predicates such as std::is_integral_v, std::is_same_v, and std::is_invocable_v answer structural questions. They do not establish semantic behavior.

Compile-Time Branching #

Use if constexpr when branches depend on a template argument. A discarded dependent branch is not instantiated, so it can contain operations unavailable for the selected type.

cpp
template <class T>
auto measure(const T& value) {
    if constexpr (std::is_integral_v<T>) {
        return value;
    } else {
        return value.size();
    }
}

Only the selected return statement is instantiated, so an integer needs no size() member. The condition itself and non-dependent code still must be well formed. Recursive specialization remains appropriate for an inductive type computation or for code that must support older language standards, but it should not obscure a direct constexpr expression.

Address Utilities and Concepts #

std::addressof(object) obtains an object’s actual address even if operator& is overloaded. std::to_address(pointer) obtains the raw address represented by a raw or pointer-like value; it does not validate lifetime, nullness, or dereferenceability.

Use concepts to constrain required operations and result properties, because behavior belongs in an interface contract:

cpp
#include <concepts>
#include <cstddef>
#include <functional>

template <class T>
concept Hashable = requires (const T& value) {
    { std::hash<T>{}(value) } -> std::convertible_to<std::size_t>;
};

Traits remain suitable for transformations and opt-in metadata. Concepts better express operations clients may perform, then allow overload resolution and diagnostics to reflect that intent.

Comments

Read or comment on GitHub (sign-in required to comment)