Function templates
A function template is a function with a type left open. The compiler fills the type in when the function is called, generating a separate function for each set of arguments it sees. That is how std::max, std::sort and std::vector work for every type without a library author writing them twice.
The type is written as a template parameter and deduced from the arguments, so the call looks like an ordinary function call. This lesson writes the smallest useful templates and then breaks the deduction on purpose to see the message.
One function, two types
The template below has a parameter list template <typename T> and a function that takes two T values and returns the larger. Nothing in the body mentions int or double: T is a placeholder the compiler replaces.
The program calls it with ints and with doubles. Each call makes the compiler instantiate the function for that type, which means two functions in the binary and one source.
typename and class mean the same thing in a template parameter list. The course writes typename, because the parameter can be an int just as easily, and the name does not have to be a class.
A template is not a function until it is used: no code is generated for larger until a call supplies a type. That is why a template that is never called costs nothing, and why an error inside one appears only at the call that instantiates it.
#include <iostream>
template <typename T>
T larger(T a, T b)
{
return a < b ? b : a;
}
int main()
{
std::cout << larger(3, 7) << '\n';
std::cout << larger(2.5, 1.5) << '\n';
}
g++ -std=c++23 -Wall -Wextra first.cpp -o first
./first
7
2.5
Naming the type yourself
The template argument can be written at the call: larger<double>(1, 2.5) asks for the double version and converts both arguments to double first. That is how a caller resolves a case the deduction cannot handle.
The program compares an int and a double through the explicit argument and prints a floating point result. Without the explicit argument the same call fails, which is the mistake in the fourth section.
Explicit arguments are also how a function is selected when the return type matters: static_cast<int>(larger<double>(...)) is a different program from larger<int>(...), and the call site says which one is meant.
#include <iostream>
template <typename T>
T larger(T a, T b)
{
return a < b ? b : a;
}
int main()
{
std::cout << larger<double>(1, 2.5) << '\n';
std::cout << larger<int>(1, 2.5) << '\n';
}
g++ -std=c++23 -Wall -Wextra explicit.cpp -o explicit_arg
./explicit_arg
2.5
2
Two type parameters
A template may have more than one parameter, and the compiler deduces each one independently. The function below takes a key and a value of different types and prints them.
The program calls it with an int and a string literal, then with a double and an int. Each combination instantiates its own function, and the compiler does the conversion the parameters ask for.
The standard library's std::pair and std::make_pair are the general form of this idea; the version here is only the printing. Lesson 060 covers the pair itself.
#include <iostream>
#include <string>
template <typename Key, typename Value>
void show(const Key& key, const Value& value)
{
std::cout << key << " = " << value << '\n';
}
int main()
{
show(1, "one");
show(2.5, 5);
}
g++ -std=c++23 -Wall -Wextra two.cpp -o two
./two
1 = one
2.5 = 5
When deduction cannot decide
A template with one parameter needs both arguments to have the same type. Passing an int and a double gives the compiler two candidates for T, and it refuses to pick one.
The message names the parameter and the two deduced types. The fix is an explicit template argument, a cast at the call, or a second template parameter for the conversion.
This is the most common template error for a beginner, and it is the reason the standard library's algorithms take iterators of the same type and let the caller convert beforehand.
template <typename T>
T larger(T a, T b)
{
return a < b ? b : a;
}
int main()
{
return larger(1, 2.5);
}
g++ -std=c++23 -Wall -Wextra conflict.cpp -o conflict
conflict.cpp: In function 'int main()':
conflict.cpp:9:18: error: no matching function for call to 'larger(int, double)'
9 | return larger(1, 2.5);
| ~~~~~~^~~~~~~~
conflict.cpp:9:18: note: there is 1 candidate
conflict.cpp:2:3: note: candidate 1: 'template<class T> T larger(T, T)'
2 | T larger(T a, T b)
| ^~~~~~
conflict.cpp:2:3: note: template argument deduction/substitution failed:
conflict.cpp:9:18: note: deduced conflicting types for parameter 'T' ('int' and 'double')
9 | return larger(1, 2.5);
| ~~~~~~^~~~~~~~
A template and an ordinary function
A function template and a non-template function can share a name. When both are viable and equally good, the non-template wins, because it needs no instantiation.
The program has a template print for any type and a plain overload for const char*. The string literal is converted by the plain one, which prints a different message, and the double goes through the template.
A library can use that rule to give a type special treatment without specialising the template: an ordinary overload is enough, and it is found before the template.
#include <iostream>
template <typename T>
void print(const T& value)
{
std::cout << "template " << value << '\n';
}
void print(const char* text)
{
std::cout << "text " << text << '\n';
}
int main()
{
print(3.5);
print("hello");
}
g++ -std=c++23 -Wall -Wextra overload.cpp -o overload
./overload
template 3.5
text hello
Exercises
- Write a template smaller() that returns the smaller of two values and call it with ints and with chars.
- Add a second template parameter to larger so that larger(1, 2.5) compiles and returns the larger value as a double.
- Call print with a std::string in overload.cpp and decide which of the two functions runs.
Questions
A new set of questions in a new order every time. Choose one answer for each, then check.
-
What is a function template?
Answer
A function with a type parameter the compiler fills in
The template is a pattern, and each call with a new type produces a function from it.
-
How is the type of a function template chosen?
Answer
It is deduced from the arguments, or written explicitly at the call
The call supplies the types, which is why
max(1, 2)andmax(1.0, 2.0)give two instantiations. -
What does the compiler produce for each new argument type?
Answer
A separate function instantiated from the template
The instantiation is a real function, which is why the template is type-safe and inlinable.
-
Why must a template definition be visible at the call site?
Answer
The compiler instantiates it there, so it needs the body
Without the definition the compiler cannot generate the function for the argument types.
-
What does this program print?
#include <iostream> template <typename T> T larger(T a, T b) { return a > b ? a : b; } int main() { std::cout << larger(7, 3) << ' ' << larger(2.5, 1.0); }Answer
7 2.5
The template is instantiated for int and double, and each call returns the larger value.
-
What does this program print?
#include <iostream> template <typename T> T half(T value) { return value / 2; } int main() { std::cout << half<double>(5) << ' ' << half<int>(5.0); }Answer
2.5 2
The explicit argument fixes T, so the int 5 becomes a double and the double 5.0 becomes an int.
-
What does this program print?
#include <iostream> #include <string> template <typename A, typename B> void show(A key, B value) { std::cout << key << " = " << value << '\n'; } int main() { show(1, "one"); show(2.5, 5); }Answer
1 = one / 2.5 = 5
The two type parameters are independent, and each line prints the pair it was given.
-
What does this program print?
#include <iostream> #include <string> template <typename T> std::string label(T) { return "template"; } std::string label(const std::string&) { return "text"; } int main() { std::cout << label(3.5) << ' ' << label(std::string{"hello"}); }Answer
template text
The template handles the double and returns its label, and the ordinary overload handles the string.
-
What does calling
larger(1, 2.5)with one type parameter report?Answer
no matching function for call to 'larger(int, double)'
The two arguments deduce different types for T, and the deduction fails.
-
What is the advantage of
std::maxbeing a template?Answer
One definition works for every type with the required comparison
The library writes the algorithm once, and the compiler generates the version for each type.
The code on this page was built with g++ 15.2.0 and run on Alpine Linux 3.24.3 (x86_64) in a Docker container. The programs whose section shows a compiler error were compiled until that error and were not run.
Article author: Arthur Isaev