Advanced templates, type traits, and if constexpr
Extend generic programming with specialization, compile-time branching, type traits, and clearer template requirements.
Learning objectives
- Distinguish full specialization from partial specialization of a class template.
- Use standard type traits such as
is_integralandremove_reference. - Explain which branch of an
if constexpris instantiated for a given type. - Recognize when a C++20 concept is clearer than a trait-based constraint.
Prerequisites: function and class templates, references, and basic compile-time constants. Examples use C++14 type aliases such as std::remove_reference_t and C++17 variable-template aliases such as std::is_integral_v and if constexpr; concepts require C++20.
From basic templates to reusable libraries
A basic template has type parameters and is instantiated for concrete types. Advanced templates add compile-time decisions: selecting a specialized implementation, checking properties of a type, and choosing code paths based on those properties.
Full and partial specialization
template <typename T>
struct TypeLabel { static constexpr const char* value = "general"; };
template <>
struct TypeLabel<bool> { static constexpr const char* value = "boolean"; };
template <typename T>
struct PointerInfo { static constexpr bool is_pointer = false; };
template <typename T>
struct PointerInfo<T*> { static constexpr bool is_pointer = true; };Full specialization supplies an implementation for one exact template argument. Partial specialization matches a family of types, such as all pointer types. Class templates can be partially specialized; function templates are generally overloaded rather than partially specialized.
Type traits
#include <type_traits> static_assert(std::is_integral_v<int>); static_assert(!std::is_integral_v<double>); static_assert(std::is_same_v<std::remove_reference_t<int&>, int>);
Type traits are compile-time tools that answer questions about types or transform them. The _t type aliases were introduced in C++14; the _v variable-template aliases were introduced in C++17. Check the specific alias and language version when using a trait.
Compile-time branching with if constexpr
#include <type_traits>
#include <iostream>
template <typename T>
void describe(const T& value) {
if constexpr (std::is_integral_v<T>) {
std::cout << "integral: " << value;
} else {
std::cout << "other type";
}
}if constexpr discards the non-selected branch during template instantiation when its condition is compile-time constant. This lets each specialization contain only the code valid for its type.
Requirements and concepts
In C++20, concepts can express template requirements directly, often more clearly than older substitution tricks. Prefer a named concept when the requirement is meaningful and reusable. Do not use advanced metaprogramming merely to make a short function look clever.
Common mistakes
- Trying to partially specialize a function template; use overloads or a constrained overload instead.
- Assuming
if constexprmakes every invalid expression acceptable; non-dependent errors can still be diagnosed. - Using type traits without understanding the exact type being inspected, including references and cv-qualifiers.
Practice
- Explain full versus partial specialization.
- What does
std::remove_reference_tproduce forlong&? - Why can if constexpr avoid compiling an invalid branch for a particular type?
Show answers
- Full specialization matches one exact argument; partial specialization matches a family.
long.- The discarded branch is not instantiated for that specialization.
Summary
Use specialization for alternate type-family behavior, type traits for compile-time type information, and if constexpr for readable compile-time branching. In C++20, concepts make requirements clearer.