| name | cpp-templates-metaprogramming |
| user-invocable | false |
| description | Use when C++ templates and metaprogramming including template specialization, SFINAE, type traits, and C++20 concepts. |
| allowed-tools | ["Read","Write","Edit","Grep","Glob","Bash"] |
C++ Templates and Metaprogramming
Template metaprogramming enables compile-time computation and code generation,
creating flexible, efficient abstractions without runtime overhead. This skill
covers function and class templates, specialization, SFINAE, type traits, and
modern concepts-based template constraints.
Function Templates
Function templates enable writing generic algorithms that work with any type
satisfying requirements.
#include <iostream>
#include <vector>
#include <string>
template<typename T>
T maximum(T a, T b) {
return (a > b) ? a : b;
}
template<typename T, typename U>
auto add(T a, U b) -> decltype(a + b) {
return a + b;
}
template<typename T, size_t N>
size_t array_size(T (&)[N]) {
return N;
}
template<typename T>
void print(T value) {
std::cout << value << "\n";
}
template<typename T>
void print(const std::vector<T>& vec) {
for (const auto& item : vec) {
std::cout << item << " ";
}
std::cout << "\n";
}
void function_template_examples() {
auto max_int = maximum(10, 20);
auto max_double = maximum(3.14, 2.71);
auto max_string = maximum(std::string("abc"), std::string("xyz"));
auto sum = add(5, 3.14);
int arr[] = {1, 2, 3, 4, 5};
std::cout << "Array size: " << array_size(arr) << "\n";
print(42);
print(std::vector<int>{1, 2, 3});
}
Class Templates
Class templates enable creating generic containers and data structures.
#include <iostream>
#include <stdexcept>
template<typename T>
class Stack {
T* data_;
size_t size_;
size_t capacity_;
public:
Stack(size_t capacity = 10)
: data_(new T[capacity])
, size_(0)
, capacity_(capacity) {}
~Stack() {
delete[] data_;
}
void push(const T& value) {
if (size_ >= capacity_) {
resize();
}
data_[size_++] = value;
}
T pop() {
if (size_ == 0) {
throw std::underflow_error("Stack is empty");
}
return data_[--size_];
}
bool empty() const { return size_ == 0; }
size_t size() const { return size_; }
:
{
capacity_ *= ;
T* new_data = T[capacity_];
( i = ; i < size_; ++i) {
new_data[i] = data_[i];
}
[] data_;
data_ = new_data;
}
};
< Key, Value>
{
Key key_;
Value value_;
:
( Key& k, Value& v) : (k), (v) {}
{ key_; }
{ value_; }
};
< T, Allocator = std::allocator<T>>
Vector {
};
{
Stack<> int_stack;
int_stack.();
int_stack.();
std::cout << int_stack.() << ;
Stack<std::string> str_stack;
str_stack.();
;
}
Template Specialization
Template specialization allows providing custom implementations for specific
types.
#include <iostream>
#include <cstring>
template<typename T>
class Container {
T value_;
public:
Container(const T& value) : value_(value) {}
void print() const {
std::cout << "Generic: " << value_ << "\n";
}
size_t memory_size() const {
return sizeof(T);
}
};
template<>
class Container<const char*> {
const char* value_;
public:
Container(const char* value) : value_(value) {}
void print() const {
std::cout << "C-string: " << value_ << "\n";
}
size_t memory_size() const {
return std::(value_) + ;
}
};
< T>
<T*> {
T* value_;
:
(T* value) : (value) {}
{
std::cout << << *value_ << ;
}
{
(T*);
}
};
{
value < ;
}
<>
<>( value) {
;
}
{
;
c();
;
c();
x = ;
;
c();
}
SFINAE (Substitution Failure Is Not An Error)
SFINAE enables compile-time function selection based on type properties.
#include <iostream>
#include <type_traits>
#include <vector>
template<typename T>
typename std::enable_if<
std::is_same<
decltype(std::declval<T>().begin()),
decltype(std::declval<T>().end())
>::value
>::type print_container(const T& container) {
std::cout << "Container: ";
for (const auto& item : container) {
std::cout << item << " ";
}
std::cout << "\n";
}
template<typename T>
typename std::enable_if<std::is_arithmetic<T>::value>::type
print_value(T value) {
std::cout << "Number: " << value << "\n";
}
template<typename T>
typename std::enable_if<!std::is_arithmetic<T>::value>::type
print_value(const T& value) {
std::cout << "Non-number: " << value << "\n";
}
< T,
= std::<std::is_integral<T>::value>>
T (T a, T b) {
(b == ) {
std::();
}
a / b;
}
{
std::cout << << *value << ;
}
{
std::cout << << value << ;
}
{
(value, std::is_pointer<T>{});
}
{
std::vector<> vec{, , };
(vec);
();
(std::());
std::cout << (, ) << ;
x = ;
(x);
(&x);
}
Type Traits
Type traits provide compile-time type information and transformations.
#include <type_traits>
#include <iostream>
#include <string>
template<typename T>
void analyze_type() {
std::cout << "Type analysis:\n";
std::cout << " Is integral: "
<< std::is_integral<T>::value << "\n";
std::cout << " Is floating point: "
<< std::is_floating_point<T>::value << "\n";
std::cout << " Is pointer: "
<< std::is_pointer<T>::value << "\n";
std::cout << " Is const: "
<< std::is_const<T>::value << "\n";
std::cout << " Size: " << sizeof(T) << "\n";
}
template<typename T>
void transform_type() {
using NoCV = std::remove_cv_t<T>;
using NoRef = std::remove_reference_t<T>;
using NoPtr = std::remove_pointer_t<T>;
using AddConst = std::add_const_t<T>;
using AddLRef = std::add_lvalue_reference_t<T>;
std::cout << "Is same after remove_cv: "
<< std::is_same<NoCV, T>::value << "\n";
}
< T>
: std::false_type {};
<>
<std::string> : std::true_type {};
<>
< *> : std::true_type {};
< T>
is_string_v = is_string<T>::value;
< T>
MakeUnsigned = std::<
std::is_signed<T>::value,
std::<T>,
T
>;
{
{
std::cout << << value << ;
} (std::is_floating_point_v<T>) {
std::cout << << value << ;
} (is_string_v<T>) {
std::cout << << value << ;
} {
std::cout << ;
}
}
{
<>();
< *>();
();
();
(std::());
}
Variadic Templates
Variadic templates enable functions and classes accepting any number of
arguments.
#include <iostream>
#include <sstream>
void print_all() {
std::cout << "\n";
}
template<typename T, typename... Args>
void print_all(T first, Args... rest) {
std::cout << first << " ";
print_all(rest...);
}
template<typename... Args>
auto sum_all(Args... args) {
return (args + ...);
}
template<typename... Args>
auto multiply_all(Args... args) {
return (args * ... * 1);
}
template<typename... Types>
class Tuple;
template<>
class Tuple<> {
public:
static constexpr size_t size = 0;
};
template<typename Head, typename... Tail>
<Head, Tail...> : Tuple<Tail...> {
Head head_;
:
size = + Tuple<Tail...>::size;
(Head h, Tail... t)
: <Tail...>(t...), (h) {}
{ head_; }
{ head_; }
{
*;
}
};
{
dummy[] = { (std::cout << args << , )... };
()dummy;
}
{
((std::cout << Is << ), ...);
std::cout << ;
}
{
(, , , std::());
total = (, , , , );
product = (, , );
;
std::cout << << (t)::size << ;
(std::make_index_sequence<>{});
}
Template Metaprogramming
Template metaprogramming performs compile-time computation using templates.
#include <iostream>
template<int N>
struct Factorial {
static constexpr int value = N * Factorial<N - 1>::value;
};
template<>
struct Factorial<0> {
static constexpr int value = 1;
};
template<int N>
struct Fibonacci {
static constexpr int value =
Fibonacci<N - 1>::value + Fibonacci<N - 2>::value;
};
template<>
struct Fibonacci<0> {
static constexpr int value = 0;
};
template<>
struct Fibonacci<1> {
static constexpr int value = 1;
};
template<typename... Types>
struct TypeList {};
template< List>
;
<... Types>
<TypeList<Types...>> {
value = ...(Types);
};
< Index, List>
;
< Index, Head, ... Tail>
<Index, TypeList<Head, Tail...>> {
type = At<Index - , TypeList<Tail...>>::type;
};
< Head, ... Tail>
<, TypeList<Head, Tail...>> {
type = Head;
};
< T, List>
;
< T>
<T, TypeList<>> : std::false_type {};
< T, Head, ... Tail>
<T, TypeList<Head, Tail...>>
: Contains<T, TypeList<Tail...>> {};
< T, ... Tail>
<T, TypeList<T, Tail...>> : std::true_type {};
{
(n <= ) ? : n * (n - );
}
{
(n <= ) ? n : (n - ) +
(n - );
}
{
fact5 = Factorial<>::value;
fib7 = Fibonacci<>::value;
std::cout << << fact5 << ;
std::cout << << fib7 << ;
MyTypes = TypeList<, , std::string>;
std::cout <<
<< Length<MyTypes>::value << ;
SecondType = At<, MyTypes>::type;
std::cout <<
<< Contains<, MyTypes>::value << ;
fact6 = ();
std::cout << << fact6 << ;
}
Concepts (C++20)
Concepts provide named constraints for template parameters with better error
messages.
#include <concepts>
#include <iostream>
template<typename T>
concept Numeric = std::integral<T> || std::floating_point<T>;
template<typename T>
concept Addable = requires(T a, T b) {
{ a + b } -> std::convertible_to<T>;
};
template<typename T>
concept Container = requires(T c) {
typename T::value_type;
typename T::iterator;
{ c.begin() } -> std::same_as<typename T::iterator>;
{ c.end() } -> std::same_as<typename T::iterator>;
{ c.size() } -> std::convertible_to<std::size_t>;
};
template<Numeric T>
T add(T a, T b) {
return a + b;
}
template<typename T>
auto square(T x) -> std::same_as<T> auto {
return x * x;
}
template<typename T>
concept Sortable = std::totally_ordered<T> && std::copyable<T>;
<Sortable T>
{
std::(values.(), values.());
}
< T>
SignedNumeric = Numeric<T> && std::signed_integral<T>;
{
std::cout << ;
}
{
std::cout << ;
}
{
result = (, );
dresult = (, );
std::vector<> vec{, , };
(vec);
();
();
}
Best Practices
- Use concepts instead of SFINAE in C++20 for clearer template constraints
- Prefer
constexpr functions over template metaprogramming for readability
- Use
std::enable_if_t and type trait _v and _t suffixes for conciseness
- Document template requirements clearly even without concepts
- Use
decltype(auto) for perfect return type deduction
- Prefer template specialization over SFINAE when full implementation differs
- Use fold expressions instead of recursive variadic templates when possible
- Mark template functions
inline or define in headers to avoid linking errors
- Use
static_assert to validate template parameters at compile time
- Prefer standard library type traits over custom implementations
Common Pitfalls
- Forgetting to define template member functions in headers, causing linker
errors
- Infinite template recursion without proper base cases
- Complex SFINAE expressions that are hard to read and maintain
- Not using
typename keyword when referring to dependent types
- Template bloat from unnecessary instantiations of large templates
- Circular dependencies in template specializations
- Ambiguous function overloads when multiple SFINAE conditions match
- Excessive compile times from complex template metaprogramming
- Not marking template
constexpr functions as constexpr
- Using templates when runtime polymorphism would be simpler and sufficient
When to Use Templates and Metaprogramming
Use templates and metaprogramming when you need:
- Generic algorithms that work with multiple types
- Compile-time computation and code generation
- Zero-overhead abstractions without runtime cost
- Type-safe interfaces with strong compile-time checking
- Containers and data structures for any type
- Expression templates for domain-specific languages
- Policy-based design with compile-time configuration
- Elimination of code duplication across similar implementations
- Static polymorphism without virtual function overhead
- Modern C++ libraries with flexible, composable components
Resources