Template Metaprogramming (TMP) is a powerful programming technique in C++ that allows computation to be performed at compile time using templates. TMP uses the C++ template mechanism (originally intended for generic programming) to generate and manipulate code during the compilation process, effectively creating programs that execute part of their logic when the code is compiled rather than at runtime.
TMP exploits the Turing-completeness of C++ templates, enabling recursive computations, type manipulations, and decision-making at compile time. This technique allows developers to optimize programs by shifting computations from runtime to compile time, enabling more efficient, flexible, and reusable code.
C++ templates allow you to write generic code that works with any data type. Templates form the basis of TMP, where types and values are manipulated at compile time.
template <typename T>
T add(T a, T b) {
return a + b;
}
TMP leverages template specialization, recursion, and constant expressions to perform calculations during compilation. The results of these computations are baked into the final binary, reducing runtime overhead.
TMP is commonly used to manipulate and inspect types at compile time. This includes techniques like type traits, SFINAE (Substitution Failure Is Not An Error), and template specialization.
TMP relies heavily on recursion (template instantiations calling themselves) and specialization (providing specific implementations for certain cases) to achieve metaprogramming logic.
In this example, the factorial of a number is computed at compile time using template recursion.
#include <iostream>
// Template metaprogram to calculate factorial at compile-time
template <int N>
struct Factorial {
static const int value = N * Factorial<N - 1>::value;
};
// Base case (factorial of 0 is 1)
template <>
struct Factorial<0> {
static const int value = 1;
};
int main() {
std::cout << "Factorial of 5: " << Factorial<5>::value << std::endl;
return 0;
}
Factorial<0> provides the base case for the recursion (Factorial<0>::value = 1).Factorial<N-1> to compute the factorial.Output:
Factorial of 5: 120
Recursion: TMP relies on recursion at compile time rather than loops. The example above demonstrates recursive factorial computation.
You can specialize a template for certain types or values to provide different behavior. This is especially useful for terminating recursive templates or handling specific cases.
template <typename T>
struct IsPointer {
static const bool value = false;
};
template <typename T>
struct IsPointer<T*> {
static const bool value = true;
};
Here, IsPointer checks whether a given type is a pointer. The general template returns false, but for pointer types (using partial specialization), it returns true.
This is a key concept in TMP, allowing templates to fail silently during substitution if certain conditions aren’t met, without causing a compilation error. It’s commonly used to enable or disable certain template functions or classes based on traits.
template <typename T>
typename std::enable_if<std::is_integral<T>::value, T>::type
print_if_integral(T value) {
std::cout << value << " is an integral type." << std::endl;
}
In this example, std::enable_if is used with std::is_integral to conditionally enable the print_if_integral function only if T is an integral type.
TMP often uses type traits to inspect and manipulate types at compile time. The C++ standard library provides a rich set of type traits in the <type_traits> header to determine characteristics like whether a type is a pointer, integral, or floating-point type.
#include <type_traits>
template <typename T>
void checkType() {
if (std::is_integral<T>::value) {
std::cout << "T is an integral type." << std::endl;
} else {
std::cout << "T is not an integral type." << std::endl;
}
}
In TMP, you can specialize a template for specific types or values to modify the behavior. Specialization allows creating different implementations based on conditions known at compile time.
template <typename T>
struct IsConst {
static const bool value = false;
};
template <typename T>
struct IsConst<const T> {
static const bool value = true;
};
Here, the primary template assumes the type T is not const, but the specialized template for const T returns true.
C++11 introduced variadic templates, which allow a function or class to accept an arbitrary number of template parameters. This is particularly useful in TMP for handling types or values in a flexible manner.
template <typename... Args>
void print(Args... args) {
(std::cout << ... << args) << std::endl; // Fold expression (C++17)
}
This variadic template allows you to print any number of arguments using TMP.
Tag dispatching is a TMP technique used to choose between different implementations of a function based on type information. It works by passing type tags as parameters to guide which implementation to select.
template <typename T>
void process(T t, std::true_type) {
std::cout << "Processing integral type" << std::endl;
}
template <typename T>
void process(T t, std::false_type) {
std::cout << "Processing non-integral type" << std::endl;
}
template <typename T>
void process(T t) {
process(t, std::is_integral<T>());
}
In this example, process selects the appropriate overload based on whether the type T is integral or not.
for loop into a series of individual instructions.