C++ Templates
Function Templates
Generic functions
template <typename T>
T max_val(T a, T b) {
return (a > b) ? a : b;
}
int m1 = max_val(3, 7); // T = int (deduced)
double m2 = max_val(3.14, 2.71); // T = double (deduced)
auto m3 = max_val<int>(3, 7); // T = int (explicit)
Templates are compiled per instantiation. max_val<int> and max_val<double> are separate functions generated by the compiler.
Class Templates
Generic container
template <typename T>
class Stack {
public:
void push(const T& value) {
data_.push_back(value);
}
T pop() {
T top = data_.back();
data_.pop_back();
return top;
}
bool empty() const { return data_.empty(); }
private:
std::vector<T> data_;
};
Stack<int> int_stack;
int_stack.push(42);
Stack<std::string> str_stack;
str_stack.push("hello");
Multiple Template Parameters
Two or more type parameters
template <typename K, typename V>
class Pair {
public:
K key;
V value;
Pair(K k, V v) : key(std::move(k)), value(std::move(v)) {}
};
auto p = Pair<std::string, int>("port", 8080);
// C++17 CTAD (class template argument deduction):
Pair p2("port", 8080); // deduced as Pair<const char*, int>
Non-Type Template Parameters
Compile-time constants as template arguments
template <typename T, size_t N>
class FixedBuffer {
public:
void set(size_t idx, T value) {
if (idx < N) data_[idx] = value;
}
T get(size_t idx) const { return data_[idx]; }
constexpr size_t size() const { return N; }
private:
T data_[N]{};
};
FixedBuffer<int, 64> buf;
buf.set(0, 42);
N is known at compile time, so the array is stack-allocated. std::array<T, N> uses this technique.
Template Specialization
Custom behavior for specific types
// Primary template
template <typename T>
std::string to_string(const T& value) {
return std::to_string(value);
}
// Specialization for std::string
template <>
std::string to_string<std::string>(const std::string& value) {
return value; // already a string
}
// Specialization for bool
template <>
std::string to_string<bool>(const bool& value) {
return value ? "true" : "false";
}
Concepts (C++20)
Constrain template parameters
#include <concepts>
// Concept: T must support < comparison
template <typename T>
concept Comparable = requires(T a, T b) {
{ a < b } -> std::convertible_to<bool>;
};
// Use the concept as a constraint
template <Comparable T>
T min_val(T a, T b) {
return (a < b) ? a : b;
}
// Shorthand syntax
auto min_val2(std::integral auto a, std::integral auto b) {
return (a < b) ? a : b;
}
Concepts replace SFINAE with readable compile-time constraints. Error messages become clear instead of template instantiation backtraces.
Variadic Templates
Templates with variable number of arguments
// Base case
void print() {}
// Recursive case
template <typename T, typename... Args>
void print(const T& first, const Args&... rest) {
std::cout << first;
if constexpr (sizeof...(rest) > 0) {
std::cout << ", ";
}
print(rest...);
}
print(1, "hello", 3.14); // "1, hello, 3.14"
// C++17 fold expression — simpler
template <typename... Args>
auto sum(Args... args) {
return (args + ...); // fold over +
}
Fold expressions (C++17) eliminate the need for recursive template expansion in many cases.