Template Metaprogramming (TMP) is a powerful C++ technique that allows you to perform computations at compile-time using templates. It's a form of "programming the compiler" that can generate highly optimized code and provide compile-time type safety.
- Compile-time computation using templates
- Type-level programming with type safety
- Code generation at compile time
- Zero-cost abstractions with no runtime overhead
- Performance: Compile-time optimizations
- Type Safety: Catch errors at compile time
- Code Generation: Reduce boilerplate code
- Generic Programming: Write type-independent algorithms
// Basic type trait
template<typename T>
struct is_pointer {
static constexpr bool value = false;
};
template<typename T>
struct is_pointer<T*> {
static constexpr bool value = true;
};
// Usage
static_assert(is_pointer<int*>::value == true);
static_assert(is_pointer<int>::value == false);// Compile-time factorial
template<unsigned n>
struct factorial {
static constexpr unsigned value = n * factorial<n-1>::value;
};
template<>
struct factorial<0> {
static constexpr unsigned value = 1;
};
// Usage
constexpr unsigned result = factorial<5>::value; // 120// Type selection based on condition
template<bool condition, typename T1, typename T2>
struct conditional {
using type = T1;
};
template<typename T1, typename T2>
struct conditional<false, T1, T2> {
using type = T2;
};
// Usage
using result_type = conditional<true, int, double>::type; // int// Enable if pattern
template<typename T, typename = void>
struct has_size_method : std::false_type {};
template<typename T>
struct has_size_method<T,
std::void_t<decltype(std::declval<T>().size())>>
: std::true_type {};
// Usage
static_assert(has_size_method<std::vector<int>>::value);
static_assert(!has_size_method<int>::value);// Compile-time sum
template<typename... Args>
struct sum;
template<typename T>
struct sum<T> {
static constexpr T value = T{};
};
template<typename T, typename... Args>
struct sum<T, Args...> {
static constexpr T value = T{} + sum<Args...>::value;
};
// Usage
constexpr int result = sum<int, 1, 2, 3, 4, 5>::value; // 15// Primary template
template<typename T>
struct type_info {
static constexpr const char* name = "unknown";
static constexpr size_t size = sizeof(T);
};
// Specializations
template<>
struct type_info<int> {
static constexpr const char* name = "int";
static constexpr size_t size = 4;
};
template<>
struct type_info<double> {
static constexpr const char* name = "double";
static constexpr size_t size = 8;
};// Modern compile-time factorial
constexpr unsigned factorial(unsigned n) {
return (n <= 1) ? 1 : n * factorial(n - 1);
}
// Usage
constexpr unsigned result = factorial(5); // 120
static_assert(factorial(5) == 120);template<typename T>
auto process(T value) {
if constexpr (std::is_integral_v<T>) {
return value * 2;
} else if constexpr (std::is_floating_point_v<T>) {
return value + 1.0;
} else {
return value;
}
}template<typename T>
concept Numeric = std::is_arithmetic_v<T>;
template<Numeric T>
T add(T a, T b) {
return a + b;
}
// Usage
auto result1 = add(5, 3); // OK
auto result2 = add(3.14, 2.0); // OK
// auto result3 = add("hello", "world"); // Compile error// Compile-time linked list
template<typename T, typename Next = void>
struct compile_time_list {
using value_type = T;
using next = Next;
};
// Usage
using list = compile_time_list<int,
compile_time_list<double,
compile_time_list<char>>>;// Type list operations
template<typename... Types>
struct type_list {};
// Get first type
template<typename List>
struct front;
template<typename T, typename... Rest>
struct front<type_list<T, Rest...>> {
using type = T;
};
// Get size
template<typename List>
struct size;
template<typename... Types>
struct size<type_list<Types...>> {
static constexpr size_t value = sizeof...(Types);
};// Compile-time sorting (bubble sort)
template<typename List>
struct sort;
template<typename T>
struct sort<type_list<T>> {
using type = type_list<T>;
};
template<typename T1, typename T2, typename... Rest>
struct sort<type_list<T1, T2, Rest...>> {
using type = std::conditional_t<
sizeof(T1) <= sizeof(T2),
typename sort<type_list<T2, Rest...>>::type,
typename sort<type_list<T2, T1, Rest...>>::type
>;
};- Compile-time computation eliminates runtime calculations
- Type information available at compile time
- Optimization opportunities for the compiler
- No dynamic allocation for compile-time structures
- Stack-based storage for all computations
- Predictable memory layout
// Avoid excessive template instantiation
template<typename T>
struct expensive_computation {
// This can cause code bloat
static constexpr auto value = /* complex computation */;
};- Large templates increase compilation time
- Complex metaprogramming can slow builds
- Debugging difficulty with template errors
- Complex syntax can be hard to understand
- Error messages are often cryptic
- Maintenance challenges for complex TMP code
// Prefer simple, readable TMP over complex solutions
template<typename T>
using remove_pointer_t = typename std::remove_pointer<T>::type;// Use constexpr instead of template metaprogramming when possible
constexpr bool is_power_of_two(unsigned n) {
return n > 0 && (n & (n - 1)) == 0;
}// Clear documentation for complex TMP
/**
* @brief Compile-time type list with push_back operation
* @tparam T Element type to add
* @tparam List Current type list
*/
template<typename T, typename List>
struct push_back;// Unit tests for TMP code
static_assert(std::is_same_v<
push_back<int, type_list<double>>::type,
type_list<double, int>
>);// Lazy evaluation for mathematical expressions
template<typename Lhs, typename Rhs>
struct add_expression {
Lhs lhs;
Rhs rhs;
auto operator[](size_t i) const {
return lhs[i] + rhs[i];
}
};// Hide complex types behind interfaces
class drawable {
struct concept {
virtual ~concept() = default;
virtual void draw() const = 0;
};
template<typename T>
struct model : concept {
T data;
void draw() const override { data.draw(); }
};
std::unique_ptr<concept> pimpl;
public:
template<typename T>
drawable(T&& obj) : pimpl(std::make_unique<model<T>>(std::forward<T>(obj))) {}
void draw() const { pimpl->draw(); }
};// Basic compile-time reflection
template<typename T>
struct reflect {
static constexpr size_t size = sizeof(T);
static constexpr bool is_trivially_copyable = std::is_trivially_copyable_v<T>;
static constexpr bool is_pod = std::is_pod_v<T>;
};- Basic templates and type traits
- Simple compile-time constants
- Template specialization
- SFINAE techniques
- Variadic templates
- Type lists and algorithms
- Expression templates
- Type erasure patterns
- Compile-time reflection
- Custom type systems
- Domain-specific languages
- Advanced optimization techniques
- "Modern C++ Design" by Andrei Alexandrescu
- "C++ Templates: The Complete Guide" by David Vandevoorde
- "Effective Modern C++" by Scott Meyers
- Type Trait Creation: Implement
is_same,is_base_of - Compile-time Math: Implement GCD, LCM, prime checking
- Type Lists: Implement
front,back,atoperations
- SFINAE Patterns: Create enable_if alternatives
- Variadic Templates: Implement tuple-like structures
- Type Algorithms: Sort types by size, find common base
- Expression Templates: Build mathematical expression system
- Type Erasure: Implement polymorphic containers
- Compile-time Reflection: Analyze class members
Template metaprogramming is a powerful tool that can generate highly optimized code and provide compile-time guarantees. Master it to write more efficient, safer, and more expressive C++ code.