Function Templates
A template is a recipe the compiler uses to generate code for whichever types you use it with. Write largest once, and it works for int, double, std::string and your own types — with full type checking and no run-time cost. Templates are how the whole standard library is built.
A first template
#include <iostream> #include <string> template <typename T> T largest(const T& a, const T& b) { return a < b ? b : a; } int main() { std::cout << largest(3, 9) << '\n'; std::cout << largest(2.5, 1.5) << '\n'; std::cout << largest(std::string{"pear"}, std::string{"apple"}) << '\n'; }
9 2.5 pear
template <typename T> introduces a type parameter. When you call largest(3, 9), the compiler deduces T = int and generates (instantiates) an int version. Each different T gets its own version.
Deduction and explicit arguments
Both arguments must deduce the same T. When they don't, give the type explicitly:
template <typename T> T largest(const T& a, const T& b) { return a < b ? b : a; } int main() { return static_cast<int>(largest(3, 2.5)); // T can't be both int and double }
main.cpp:5:29: error: no matching function for call to 'largest'
#include <iostream> template <typename T> T largest(const T& a, const T& b) { return a < b ? b : a; } int main() { std::cout << largest<double>(3, 2.5) << '\n'; // explicit: T = double }
3
Templates over containers
Templates really shine for algorithms that work on any container of any element type:
#include <iostream> #include <list> #include <string> #include <vector> template <typename Container> void print_all(const Container& c, const std::string& separator = ", ") { bool first = true; for (const auto& item : c) { if (!first) std::cout << separator; std::cout << item; first = false; } std::cout << '\n'; } template <typename Container, typename Predicate> int count_matching(const Container& c, Predicate pred) { int n = 0; for (const auto& item : c) if (pred(item)) ++n; return n; } int main() { std::vector<int> v{1, 2, 3, 4, 5, 6}; std::list<std::string> words{"red", "green", "blue"}; print_all(v); print_all(words, " | "); std::cout << count_matching(v, [](int x) { return x % 2 == 0; }) << " even numbers\n"; }
1, 2, 3, 4, 5, 6 red | green | blue 3 even numbers
Passing the predicate as a template parameter lets the compiler inline the lambda — faster than std::function.
Abbreviated templates
C++20 lets you write auto parameters in ordinary functions; each one is an implicit template parameter:
#include <iostream> auto add(const auto& a, const auto& b) { return a + b; } int main() { std::cout << add(2, 3) << ' ' << add(1.5, 2) << '\n'; }
5 3.5
Non-type template parameters
Template parameters can also be values known at compile time, such as sizes:
#include <array> #include <iostream> template <std::size_t N> double average(const std::array<double, N>& values) { double sum = 0; for (double v : values) sum += v; return sum / N; } int main() { std::array<double, 4> a{1, 2, 3, 4}; std::array<double, 2> b{10, 20}; std::cout << average(a) << ' ' << average(b) << '\n'; }
2.5 15
Errors in templates
A template only fails when it's instantiated with a type that doesn't support what the body does — for example calling largest with a type that has no <. Older compilers produced notoriously long error messages for this. C++20 concepts (lesson 41) let you state the requirements up front, giving short, clear errors.
Where templates live
Because the compiler needs the full template definition to generate code, templates are normally defined entirely in header files, not split into a header and a source file.
Try it yourself
Write a template clamp_all that limits every element of a vector to a range, and use it on ints and doubles.
0 40 100 75 | 0 0.5 1