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.
#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, andstd::remove_cvref_tremove qualifications for type comparison.std::decay_tadditionally performs array-to-pointer and function-to-pointer adjustments, matching by-value argument passing.std::conditional_tchooses one of two types without defining separate specializations.std::common_type_tcomputes 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.
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:
#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)