Templates & Concepts
Suppose you need a function that returns the larger of two values. Without templates you would write one version for int, another for double, another for std::string, all with identical bodies. A template lets you write the code once with a placeholder type, and the compiler creates a version for each type you actually use. The whole standard library, including std::vector and std::max, is built from templates.
In this lesson you will write function and class templates, use non-type template parameters, and then make templates safer and clearer with concepts, one of the most important features of C++20.
Function templates
Start with template <typename T>, then write the function using T wherever a type is needed. When you call it, the compiler deduces T from the arguments.
#include <print>
#include <string>
template <typename T>
T largest(T a, T b) {
return (a > b) ? a : b;
}
int main() {
std::println("{}", largest(12, 45)); // T is int
std::println("{}", largest(3.5, 2.25)); // T is double
std::println("{}", largest(std::string("Jaipur"), std::string("Pune")));
std::println("{}", largest<double>(3, 4.5)); // T chosen by you
// largest(3, 4.5); // error: T cannot be both int and double
}45 3.5 Pune 4.5
The compiler generates real functions, such as largest<int> and largest<double>, only for the types you use. This is called instantiation, and it means templates cost nothing at run time: the generated code is as fast as a hand-written version. In the commented line the two arguments suggest different types for T, so deduction fails; writing largest<double> picks the type explicitly.
Since C++20 there is also a short form. A parameter declared as auto makes the function a template automatically, and each auto parameter can be a different type:
auto add(auto a, auto b) {
return a + b;
}
// add(2, 3) is 5; add(2, 0.5) is 2.5Class templates
A class can be a template too. Here is a simple stack, a container where the last item pushed is the first popped, like a pile of plates.
#include <print>
#include <string>
#include <vector>
template <typename T>
class Stack {
public:
void push(const T& value) { items_.push_back(value); }
T pop() {
T top = items_.back();
items_.pop_back();
return top;
}
bool empty() const { return items_.empty(); }
std::size_t size() const { return items_.size(); }
private:
std::vector<T> items_;
};
int main() {
Stack<std::string> plates;
plates.push("Red");
plates.push("Blue");
plates.push("Green");
std::println("Top plate: {}", plates.pop());
std::println("Plates left: {}", plates.size());
Stack<int> undo;
undo.push(7);
std::println("Undo step: {}", undo.pop());
}Top plate: Green Plates left: 2 Undo step: 7
For a class template you normally write the type in angle brackets, as in Stack<int>. Since C++17, class template argument deduction (CTAD) lets the compiler work it out from constructor arguments when it can, which is why std::vector v{1, 2, 3}; creates a std::vector<int>.
Non-type template parameters
A template parameter can also be a value, such as a size. std::array<int, 7> uses one. Here a function works with an array of any type and any length:
#include <array>
#include <print>
template <typename T, std::size_t N>
T sum(const std::array<T, N>& values) {
T total{};
for (const auto& v : values) {
total += v;
}
return total;
}
int main() {
std::array<int, 4> overs = {6, 11, 4, 9};
std::array<double, 3> weights = {1.5, 2.25, 0.75};
std::println("{} {}", sum(overs), sum(weights));
}30 4.5
T total{} starts at zero for numbers (and empty for strings). Both T and N are deduced from the argument.
Concepts: saying what a template needs
A plain template accepts any type, even one that makes no sense. If you call a gcd function with double values, the error appears deep inside the template, often in many confusing lines. A concept (C++20) is a named requirement on a type. The standard library provides many in <concepts>, such as std::integral, std::floating_point and std::totally_ordered.
#include <concepts>
#include <print>
template <std::integral T>
T gcd_of(T a, T b) {
while (b != 0) {
T remainder = a % b;
a = b;
b = remainder;
}
return a;
}
double average(std::integral auto a, std::integral auto b) {
return (a + b) / 2.0;
}
int main() {
std::println("gcd(84, 36) = {}", gcd_of(84, 36));
std::println("average = {}", average(7, 10));
// gcd_of(8.5, 2.5); // error: constraints not satisfied (double is not integral)
}gcd(84, 36) = 12 average = 8.5
template <std::integral T> means "T must be an integer type". The short form std::integral auto does the same for a single parameter. With a wrong type, the compiler now gives a short message pointing at the call and saying which concept was not satisfied.
You can also write your own concepts with a requires expression, which lists operations the type must support:
#include <concepts>
#include <print>
#include <string>
template <typename T>
concept HasArea = requires(const T& shape) {
{ shape.area() } -> std::convertible_to<double>;
};
struct Square {
double side;
double area() const { return side * side; }
};
struct Label {
std::string text;
};
void print_area(const HasArea auto& shape) {
std::println("Area: {}", shape.area());
}
int main() {
print_area(Square{4});
// print_area(Label{"Hi"}); // error: Label does not satisfy HasArea
}Area: 16
HasArea accepts any type with a const member function area() whose result converts to double. Unlike virtual functions, this works at compile time and needs no common base class.
Common mistakes
- Mixing types in deduction.
largest(3, 4.5)fails; convert one argument or writelargest<double>(...). - Putting template code in a
.cppfile. Other files cannot instantiate it, and linking fails. - Forgetting the angle brackets.
Stack s;fails when nothing can be deduced; writeStack<int> s;. - Assuming operations exist. A template that uses
<fails for types without<; express the need with a concept such asstd::totally_ordered.
Key points
- A function template (
template <typename T>) is a recipe the compiler instantiates for each type used;autoparameters (C++20) are a short form. - Class templates, such as
Stack<T>andstd::vector<T>, take types in angle brackets; CTAD can deduce them. - Templates can take values too, such as the
Ninstd::array<T, N>. - Concepts (C++20) state what a template needs, using standard ones like
std::integralor your ownrequiresexpressions. - Template definitions belong in headers.
Exercise
Write a function template count_above that takes a const std::vector<T>& and a limit of type T, and returns how many items are greater than the limit. Constrain T with std::totally_ordered. Test it with marks {45, 78, 92, 60, 88} and limit 75, and with the city names {"Agra", "Pune", "Delhi", "Surat"} and limit "Mumbai".
Show solution
#include <concepts>
#include <print>
#include <string>
#include <vector>
template <std::totally_ordered T>
std::size_t count_above(const std::vector<T>& items, const T& limit) {
std::size_t count = 0;
for (const auto& item : items) {
if (item > limit) {
++count;
}
}
return count;
}
int main() {
std::vector<int> marks = {45, 78, 92, 60, 88};
std::vector<std::string> cities = {"Agra", "Pune", "Delhi", "Surat"};
std::println("Marks above 75: {}", count_above(marks, 75));
std::println("Cities after Mumbai: {}", count_above(cities, std::string("Mumbai")));
}Marks above 75: 3 Cities after Mumbai: 2
78, 92 and 88 are above 75. Alphabetically, "Pune" and "Surat" come after "Mumbai". The second call wraps the limit in std::string so that both arguments agree on T.
Quick Quiz
1. What does the compiler do with template <typename T> T largest(T a, T b) when you call largest(2, 9)?
Templates are instantiated at compile time for the deduced types, with no run-time cost.
2. Why do template definitions usually go in header files?
Each file that uses a template must see its definition to instantiate it.
3. What does template <std::integral T> mean?
std::integral is satisfied by all integer types, and the template rejects others with a clear error.
4. Which C++ version introduced concepts?
Concepts, along with abbreviated auto parameters, arrived in C++20.
Learn C++ with a mentor
Free lessons get you started. Our Java, C & C++ Programming training adds real projects and code reviews from working developers.
- Project-based learning
- Mentor code reviews
- Free counselling call
