Essential template metaprogramming
The rendering of the source code in this post may not be perfect, select the text instead.
C++ templates enable generic programming by allowing functions/classes to operate with any type.
template<typename T>
Type T
is deduced at compile-time. Templates support multiple parameters and default arguments.
Template specialization #
Custom implementations can be defined for specific types. Partial specialization works with classes for specific behavior in subsets of types.
template<> void func<int>(int n) {}
Full specialization handles exact type matches.
Variadic templates #
Introduced in C++11, variadic templates accept a variable number of template arguments.
template<typename... Args>
Use sizeof...(Args)
to count arguments.
Recursive instantiation processes each argument individually.
Type traits #
Type traits, defined in <type_traits>
, inspect/modify type properties at compile-time, e.g. std::is_integral<T>
, std::remove_reference<T>
. Used in conjunction with static_assert
for compile-time validation.
SFINAE (Substitution Failure Is Not An Error) #
SFINAE allows invalid template specializations to be discarded without causing compilation errors. Commonly used with std::enable_if
to conditionally enable/disable templates based on type properties.
constexpr
and templates #
constexpr
enables compile-time evaluation of functions/variables. It is combined with templates for defining slightly advanced compile-time computations. E.g.
constexpr int factorial(int n);
The above definition can compute factorials at compile-time.
Template metaprogramming #
TMP uses templates to perform computations at compile-time. Techniques include recursive template instantiation, type manipulation, and compile-time algorithms. A common example is computing the n-th Fibonacci sequence at compile-time.
Concepts (C++20) #
Concepts constrain template parameters, improving readability and error tracing.
template<typename T> requires std::integral<T>;
Concepts replace SFINAE in many cases, which is very useful in conditionally generic structures.
Fold expressions #
Fold expressions simplify variadic template parameter packs.
(args + ...) // for binary operations
Supported operations are +
, -
, *
, /
, &&
, ||
, ,
.
It essentially reduces boilerplate in recursive template implementations.
CRTP (Curiously Recurring Template Pattern) #
CRTP is a case where a class template derives from a specialization of itself.
template<typename T> class base {};
class derived : public base<derived> {};
CRTP is mostly used in static polymorphism and method injection.
Template aliases #
The using
keyword creates template aliases.
template<typename T> using vec = std::vector<T>;
Aliases improve readability and limits redundancy in template-heavy code (certainly not the contrived example above, which only serves to illustrate the point).
Non-type template parameters #
Templates can accept non-type parameters like integers, pointers, or enums.
template<int N>
This is applicable in fixed-size containers or compile-time constants e.g. std::array<T, N>
.
Template argument deduction #
C++17 introduced class template argument deduction (CTAD), allowing constructors to deduce template parameters.
std::pair p(1, 2.0); // deduces std::pair<int, double>
CTAD reduces explicit type specifications.
Template metafunctions #
Metafunctions are templates that operate on types/values at compile-time.
template<typename T> struct identity { using type = T; };
It mostly appears in TMP for type transformations.
Template lambda (C++20) #
Lambdas can now be templated, which is ideal in generic lambda expressions.
auto lambda = []<typename T>(T x) {};
Common when inlining generic operations without defining separate templates. For a while, the only other way to deduce this type would be to use decltype
and SFINAE shenanigans.
Template constraints with requires
#
C++20 requires
clause adds constraints to templates, forcing parameters meet specific criteria.
template<typename T> requires std::floating_point<T>;
It's the same thing as the Concepts that we mentioned.
Template parameter packs expansion #
Parameter packs can be expanded in multiple contexts: function arguments, initializer lists, and template arguments.
func(args...);
Pack expansions enable concise template implementations.
Template recursion limits #
Compilers impose limits on template recursion depth. Exceeding this limit results in compilation errors. Use #pragma
or compiler flags to adjust limits, but I suggest iterative TMP where applicable.
Template instantiation control #
Explicit template instantiation prevent compile-time overhead by pre-generating specific template instances.
template class klass<int>;
Many large projects use this approach to manage compilation times.
Whatever we covered so far is what will mostly appear in actual source code.
Aside, libraries like Boost.MPL
and Brigand
provide pre-built metaprogramming utilities, e.g. type lists, compile-time algorithms, for less boilerplate in TMP, in case you use any of those.
References #
There are no references, I am the reference.
- Previous: Padding and alignment in structs
- Next: Cooperative multitasking in Rust