C++: Introduction to Template Metaprogramming
Last updated: 2026-08-26
In lesson 46 we learned about advanced smart pointers.
Now, we'll touch upon C++'s "dark magic" — template metaprogramming.
Template metaprogramming is a technique that executes code at compile time, which can significantly improve runtime performance.
1. Template Metaprogramming Overview
(1) 1.1 What Is Template Metaprogramming?
Template Metaprogramming (TMP) is a technique that uses templates to perform computations at compile time.
Characteristics:
- Computations are done at compile time, with zero overhead at runtime
- Code is complex, and compilation times are long
- Error messages are hard to read
(2) 1.2 Why Use Template Metaprogramming?
| Advantage | Description |
|---|---|
| Performance | Compile-time computation, faster at runtime |
| Type safety | Compile-time type checking |
| Generics | Write truly generic code |
2. Compile-Time Computation
(1) 2.1 Compile-Time Factorial
Example: Computing factorial with templates (Difficulty ⭐⭐⭐)
▶ Example 1: Code Example (Difficulty ⭐)
#include <iostream>
// Primary template
template<int N>
struct Factorial {
static const int value = N * FactorialN-1::value;
};
// Specialization: termination condition
template<>
struct Factorial<0> {
static const int value = 1;
};
int main() {
std::cout << "5! = " << Factorial<5>::value << std::endl; // 120
// Result computed at compile time, used directly at runtime
return 0;
}
Output:
5! =
▶ Example 2: Code Example (Difficulty ⭐)
💡 Tip:
- Use template specialization as the recursion termination condition
static const int valueis a compile-time constant
(2) 2.2 constexpr Functions (C++11, Recommended)
C++11 introduced constexpr, making compile-time computation simpler.
Example: Computing factorial with constexpr (Difficulty ⭐⭐)
#include <iostream>
constexpr int factorial(int n) {
return n <= 1 ? 1 : n * factorial(n - 1);
}
int main() {
constexpr int result = factorial(5); // Compile-time computation
std::cout << "5! = " << result << std::endl; // 120
return 0;
}
Output:
5! =
💡 Tip:
constexpris more concise than template metaprogramming — preferconstexpr
3. Type Traits
(1) 3.1 What Are Type Traits?
Type Traits are techniques for querying or modifying type information at compile time.
Example: Using type_traits to check types (Difficulty ⭐⭐)
#include <iostream>
#include <type_traits>
int main() {
std::cout << std::is_integralint::value << std::endl; // 1 (true)
std::cout << std::is_integraldouble::value << std::endl; // 0 (false)
std::cout << std::is_pointer<int*>::value << std::endl; // 1 (true)
return 0;
}
(2) 3.2 Custom Type Traits
Example: Detecting whether a class has a member function (Difficulty ⭐⭐⭐⭐)
#include <iostream>
#include <type_traits>
// Detect whether there is a serialize member function
template<typename T>
struct has_serialize {
private:
template<typename U>
static auto test(int) -> decltype(std::declvalU().serialize(), std::true_type{});
template<typename U>
static std::false_type test(...);
public:
static const bool value = decltype(testT(0))::value;
};
struct Person {
void serialize() {}
};
int main() {
std::cout << has_serializePerson::value << std::endl; // 1 (true)
std::cout << has_serializeint::value << std::endl; // 0 (false)
return 0;
}
4. SFINAE
(1) 4.1 What Is SFINAE?
SFINAE (Substitution Failure Is Not An Error): When template argument substitution fails, it is not an error — the compiler will try other overloads.
Purpose: Select different function overloads based on type characteristics.
(2) 4.2 Example: SFINAE for Selecting Overloads (Difficulty ⭐⭐⭐⭐)
#include <iostream>
#include <type_traits>
#include <string>
// Version 1: for integer types
template<typename T>
typename std::enable_if<std::is_integralT::value, std::string>::type
toString(T value) {
return std::to_string(value) + " (integer)";
}
// Version 2: for other types
template<typename T>
typename std::enable_if<!std::is_integralT::value, std::string>::type
toString(T value) {
return "Not an integer";
}
int main() {
std::cout << toString(42) << std::endl; // 42 (integer)
std::cout << toString(3.14) << std::endl; // Not an integer
return 0;
}
Output:
(program output)
5. Variadic Templates
(1) 5.1 What Are Variadic Templates?
Variadic Templates allow templates to accept an arbitrary number of arguments.
Example: Printing any number of arguments (Difficulty ⭐⭐⭐)
#include <iostream>
// Recursion termination function
void print() {
std::cout << std::endl;
}
// Recursive print
template<typename T, typename... Args>
void print(T first, Args... rest) {
std::cout << first << " ";
print(rest...); // Recursive call
}
int main() {
print(1, 2.5, "hello", 'a');
// Output: 1 2.5 hello a
return 0;
}
6. C++17 Fold Expressions
(1) 6.1 Basic Usage
C++17 introduced fold expressions, which simplify variadic templates.
Example: Summation (Difficulty ⭐⭐)
#include <iostream>
template<typename... Args>
auto sum(Args... args) {
return (args + ...); // Fold expression
}
int main() {
std::cout << sum(1, 2, 3, 4, 5) << std::endl; // 15
return 0;
}
▶ Example 3: constexpr Compile-Time Computation (Difficulty ⭐)
#include <iostream>
// constexpr functions are evaluated at compile time
constexpr int square(int n) {
return n * n;
}
constexpr int cube(int n) {
return n * n * n;
}
int main() {
// Compile-time computation, results embedded directly
constexpr int sq = square(5);
constexpr int cb = cube(3);
std::cout << "5^2 = " << sq << std::endl;
std::cout << "3^3 = " << cb << std::endl;
// Static assertions verify compile-time evaluation
static_assert(sq == 25, "square(5) should be 25");
static_assert(cb == 27, "cube(3) should be 27");
return 0;
}
Output:
5^2 =
3^3 =
❓ FAQ
Q: Is template metaprogramming hard to learn? A: Yes. It's recommended to master basic templates first before learning metaprogramming. Most projects don't need metaprogramming.
constexpr, and many compile-time computations can now be implemented with constexpr functions.📖 Summary
| Topic | Key Points |
|---|---|
| Compile-time computation | Template recursion or constexpr |
| Type traits | type_traits library |
| SFINAE | Select overloads based on type |
| Variadic templates | Accept arbitrary number of arguments |
| Fold expressions | C++17, simplifies variadic templates |
📝 Exercises
-
Basic (Difficulty ⭐): Write a constexpr function to compute factorial, evaluated at compile time. Verify the result using static_assert.
-
Intermediate (Difficulty ⭐⭐): Use std::enable_if to implement a function template that is only enabled when T is an integer type. Floating-point types should cause a compilation error.
-
Challenge (Difficulty ⭐⭐⭐): Use variadic templates to implement a print_all function that accepts any number and type of arguments and prints them one by one. Hint: Use recursive expansion or fold expressions (C++17).
- Template metaprogramming: compile-time computation, zero runtime overhead
- constexpr compile-time evaluation
- SFINAE: Substitution Failure Is Not An Error
- Type traits query type properties
- Variadic templates handle arbitrary numbers of arguments
Next lesson: C++17/20 New Features (#48)