C++ Templates
Introduction to Templates
Templates in C++ are a core feature of generic programming that let functions and classes work with generic types. Instead of writing separate versions of code for each type, you write a single definition that the compiler instantiates for the specific types you use.
They are heavily used throughout the C++ standard library (often colloquially called the STL), for example in containers such as std::vector
, std::list
, and std::map
.
Function Templates
Function templates allow defining one function for multiple data types. The actual type is deduced when the function is called. With a single type parameter, both arguments must deduce to the same type.
Syntax:
template <typename T>
T function_name(T param1, T param2) { /* ... */ }
#include <iostream>
// Same-type add: both arguments must deduce to the same T
template <typename T>
T add(T a, T b) {
return a + b; // requires operator+ for T
}
int main() {
std::cout << add(5, 3) << '\n'; // int -> 8
std::cout << add(2.5, 1.2) << '\n'; // double -> 3.7
// std::cout << add(1, 2.5) << '\n'; // error: deduces T=int vs T=double
}
8 3.7
Mixed-Type Deduction
When argument types differ, use two template parameters and let the compiler deduce the result type from the expression:
#include <iostream>
#include <type_traits>
// C++11+: deduce result type from a + b
template <typename T, typename U>
auto add_mixed(T a, U b) -> decltype(a + b) {
return a + b;
}
// C++14+: trailing return can be omitted (auto deduces)
template <typename T, typename U>
auto add_mixed2(T a, U b) { return a + b; }
int main() {
std::cout << add_mixed(1, 2.5) << '\n'; // 3.5 (double)
std::cout << add_mixed2(1u, 2) << '\n'; // 3 (unsigned int + int -> implementation-defined usual arithmetic conversions)
}
Class Templates
Class templates make it possible to write generic classes that can store and operate on any type.
#include <iostream>
#include <string>
template <typename T>
class Box {
T value{};
public:
explicit Box(T v) : value(v) {}
const T& getValue() const { return value; }
};
int main() {
Box<int> intBox(42);
Box<std::string> strBox("Hello");
std::cout << intBox.getValue() << '\n';
std::cout << strBox.getValue() << '\n';
}
42 Hello
Multiple Template Parameters
Templates can accept more than one type parameter, enabling flexible and reusable data structures. In C++17, class template argument deduction (CTAD) can sometimes infer the template arguments from constructor arguments.
#include <iostream>
#include <string>
template <typename T1, typename T2>
struct Pair {
T1 first;
T2 second;
Pair(T1 a, T2 b) : first(a), second(b) {}
};
int main() {
Pair<int, std::string> p(1, std::string("one")); // explicit args
std::cout << p.first << ", " << p.second << '\n';
// C++17 CTAD (no template args):
Pair p2(1, std::string("one")); // deduces Pair<int, std::string>
// Beware: using a string literal would deduce const char* instead of std::string
Pair p3(1, "one"); // deduces Pair<int, const char*>
std::cout << p2.second << ", " << p3.second << '\n';
}
1, one one, one
Template Parameter Kinds
// Non-type parameter example
#include <array>
template <std::size_t N>
struct Buffer { std::array<int, N> data{}; };
Buffer<16> buf; // compile-time sized storage
Kind | Example | Notes |
---|---|---|
Type parameter | template<typename T> | typename and class are interchangeable in parameter lists. |
Non-type parameter | template<std::size_t N> | Since C++20, many literal types are allowed (not just integrals); commonly used with std::array<T,N> . |
Template template parameter | template<template<typename> class C> | Allows passing a template as a parameter (e.g., an allocator or container). |
Constrained Templates (C++20 Concepts)
Concepts let you express requirements on template arguments, producing clearer errors and preventing invalid instantiations.
#include <concepts>
#include <iostream>
// Constrain to arithmetic-like types
template <typename T>
requires std::integral<T> || std::floating_point<T>
T add_numbers(T a, T b) { return a + b; }
int main() {
std::cout << add_numbers(2, 3) << '\n'; // ok
// add_numbers(std::string{"a"}, std::string{"b"}); // error: constraints not satisfied
}
Best Practices
- Place template definitions (not just declarations) in headers, or use explicit instantiation if separating.
- Prefer clear, descriptive parameter names (TKey
, TValue
) when multiple types are involved.
- Constrain templates (C++20 concepts or SFINAE) to express requirements early and improve diagnostics.
- Keep implementations readable; refactor complex meta-programming into helper types/functions.
- Be aware of ADL and conversions in overloaded function templates to avoid surprises.
Common Pitfalls
- **Linker errors**: Only declaring a template in a header but defining it in a .cpp will cause undefined references in users of the template.
- **Type deduction surprises**: Single-parameter function templates require both arguments to have the same deduced type; use mixed-type forms or explicit template arguments when needed.
- **CTAD traps**: String literals deduce to const char*
; wrap in std::string
if that's what you want.