Overview

11 Template metaprogramming

This chapter shows how C++ templates enable programs that run at compile time to manipulate types and guide code generation, yielding faster and safer generic libraries. It motivates metaprogramming as a way to tailor algorithms to type capabilities and highlights modern C++17 features that make this style practical: compile-time branching with constexpr if, rich type traits, and SFINAE-based introspection. Alongside type-centric techniques, the chapter also emphasizes uniformly invoking arbitrary callables and illustrates how these tools support higher-level abstractions, from generic utilities to small domain-specific languages.

It begins with deducing and transforming types: using decltype and std::declval to infer element types, then normalizing results with remove_cv and remove_reference. The chapter demonstrates verifying deductions via static_assert and building core traits by partial specialization (for example, custom is_same and remove_reference). It then advocates relying on container-provided value_type when available, and shows how to detect such metainformation and other properties (like iterability) with void_t and SFINAE. Combined with constexpr if, these checks let code select the right implementation while compiling only valid branches, preventing hard-to-read overload sets and eliminating invalid code paths.

Next, the text develops a generic currying wrapper that stores a callable and captured arguments, decides at compile time (with is_invocable) whether enough arguments are present, and either invokes or returns a new curried object. To support every kind of callable—including member pointers—it uses std::invoke, and pairs it with std::apply to expand argument tuples; reference wrappers avoid unnecessary copies. Finally, it builds a compact DSL for transactional record updates by modeling an AST (field, update, transaction), overloading operators to compose updates, applying them atomically via copy-and-swap, and distinguishing setters that return bool from those that return void using is_invocable_r. Together, these patterns showcase how template metaprogramming and C++17 features enable expressive, type-safe APIs with zero runtime overhead.

When you provide different arguments to is_same, only the first definition that returns false_type will be a match. If you pass the same type as both parameters, both definitions will be a match, and the more specialized match will win—the one returning true_type.
c11_01.png
The void_t metafunction is strange: it ignores all its parameters and always returns void. It’s useful because it can be evaluated only if the types you pass as its arguments are valid. If they aren’t, void_t triggers a substitution failure, and the compiler will ignore the definition that used it.
c11_02.png
The abstract syntax tree you want to model contains three levels: the object you’re updating, a list of updates that need to be performed (if each update contains two items, the field that should be updated), and the new value the field should have.
c11_03.png

Summary

  • Templates give you a Turing-complete programming language that is executed during program compilation. This was discovered accidentally by Erwin Unruh, who created a C++ program that prints the first 10 prime numbers during compilation—as compilation errors.
  • TMP is not only a Turing-complete language; it’s also a pure functional language. All variables are immutable, and there’s no mutable state in any form.
  • The type_traits header contains many useful metafunctions for type manipulation.
  • From time to time, because of limitations or missing features in the core programming language, workarounds are added to the standard library. For example, std::invoke allows you to call all function-like objects, even those that don’t support the regular function-call syntax.
  • DSLs are tedious to write but let you significantly simplify the main program logic. Ranges are also DSLs, in a sense; they define an AST for defining range transformations using the pipe syntax.

FAQ

What is template metaprogramming (TMP) in C++, and why would I use it?Template metaprogramming is writing code that runs during compilation to compute with types and generate specialized code. It’s useful for optimized generic code: you can select algorithms, check type properties, and transform types at compile-time, leading to better performance and safer APIs without runtime overhead.
What is a metafunction, and how is it different from a normal function?A metafunction is a template that operates on types (and sometimes integral constants) instead of runtime values. It “returns” a type (e.g., via a nested ::type or a using alias) or a compile-time value (e.g., std::true_type/std::false_type). You invoke it by instantiating the template, not by calling it at runtime.
How can I get the element type of a collection at compile time?Use a metafunction based on decltype and iterators, and strip qualifiers: using contained_type_t<C> = std::remove_cv_t<std::remove_reference_t<decltype(*begin(std::declval<C>()))>>; It works even when the collection isn’t default-constructible because it uses std::declval. Prefer a collection’s C::value_type when available.
Why does decltype(*begin(c)) give me const T& instead of T?decltype preserves cv-qualifiers and references; dereferencing an iterator typically yields a reference (often const T&). If you want the “plain” type, remove qualifiers and references with std::remove_cv_t and std::remove_reference_t.
How do I “debug” a deduced type at compile time?- Instantiate an incomplete template to force the compiler to print the deduced type: template<typename T> class error; error<SomeType> x;
- Assert expected types with static_assert(std::is_same<A,B>::value, "message"); (or the C++17 shortcut std::is_same_v<A,B>).
What is void_t and how does it relate to SFINAE?template<typename...> using void_t = void; It always aliases void but only if its arguments are well-formed. In SFINAE contexts this lets you detect traits. Example: detect nested value_type by specializing on void_t<typename C::value_type> to choose a “true” branch; otherwise the primary template yields “false.”
How can I detect if a type is iterable?Use void_t with decltype and std::declval to check for begin/end and a dereferenceable iterator: specialize when decltype(*begin(std::declval<C>())) and decltype(end(std::declval<C>())) are well-formed. Combine with if constexpr to select implementations safely.
When should I use if constexpr instead of a normal if in templates?Use if constexpr when the condition depends on compile-time information and only the selected branch needs to be instantiated. This avoids invalid code in the non-taken branch from causing errors (e.g., calling a function that exists only for certain types).
What’s the difference between std::invoke and std::apply, and why do they matter?- std::invoke uniformly calls any callable (function objects, function pointers, pointers to member functions/variables) with normal arguments.
- std::apply calls a callable using arguments stored in a std::tuple. It’s perfect when you accumulate arguments (e.g., in a curried function) and need to call the original function with them.
How do I implement a generic currying utility, and what should I watch out for?- Store the target callable and captured arguments by value in a std::tuple (use std::decay_t).
- On each call, append new arguments; if std::is_invocable_v with all captured args is true, std::apply the function; otherwise return a new curried object with the extended tuple.
- Use std::invoke/std::apply for universal call support.
- Use std::ref/std::cref if you must keep references to noncopyable or heavy objects.

pro $24.99 per month

  • access to all Manning books, MEAPs, liveVideos, liveProjects, and audiobooks!
  • choose one free eBook per month to keep
  • exclusive 50% discount on all purchases
  • renews monthly, pause or cancel renewal anytime

lite $19.99 per month

  • access to all Manning books, including MEAPs!

team

5, 10 or 20 seats+ for your team - learn more


choose your plan

team

monthly
annual
$49.99
$499.99
only $41.67 per month
  • five seats for your team
  • access to all Manning books, MEAPs, liveVideos, liveProjects, and audiobooks!
  • choose another free product every time you renew
  • choose twelve free products per year
  • exclusive 50% discount on all purchases
  • renews monthly, pause or cancel renewal anytime
  • renews annually, pause or cancel renewal anytime
  • Functional Programming in C++ ebook for free
choose your plan

team

monthly
annual
$49.99
$499.99
only $41.67 per month
  • five seats for your team
  • access to all Manning books, MEAPs, liveVideos, liveProjects, and audiobooks!
  • choose another free product every time you renew
  • choose twelve free products per year
  • exclusive 50% discount on all purchases
  • renews monthly, pause or cancel renewal anytime
  • renews annually, pause or cancel renewal anytime
  • Functional Programming in C++ ebook for free
choose your plan

team

monthly
annual
$49.99
$499.99
only $41.67 per month
  • five seats for your team
  • access to all Manning books, MEAPs, liveVideos, liveProjects, and audiobooks!
  • choose another free product every time you renew
  • choose twelve free products per year
  • exclusive 50% discount on all purchases
  • renews monthly, pause or cancel renewal anytime
  • renews annually, pause or cancel renewal anytime
  • Functional Programming in C++ ebook for free