The Problem
Let’s say we have a function that clamps a value betwen a range.
int clamp(int value, int lower, int upper)
{
if (value < lower) {
return lower;
}
if (value > upper) {
return upper;
}
return value;
}
Nice. What about if we want to apply this to double‘s? Well, you let’s overload it.
double clamp(double value, double lower, double upper)
{
if (value < lower) {
return lower;
}
if (value > upper) {
return upper;
}
return value;
}
The implementation is largely the same. This adds code bloat.
What we need is a universal function that can accept a wide range of parameters.
Fortunately, C++ supports has a feature that was designed specifically to solve this kind of problem.
Introducing C++ templates!
The template system was specifically designed to make functions (and classes) handle different types. It’s used to facilitate generic programming.
The template uses a placeholder type. In fact, the actual type isn’t known when the template is implemented.
Once a template is defined, the compiler can use the template to generate as many overloaded functions (or classes) as needed, each using different actual types!
Function templates
A function template is a function-like definition that is used to generate one or more overloaded functions, which are compatible with different types.
The initial function template is called the primary template. Each overloaded function that uses the primary template are called instantiated functions.
Let’s convert the clamp() function above into a function template!
Creating the clamp() function template
Here’s the original function
int clamp(int value, int lower, int upper)
{
if (value < lower) {
return lower;
}
if (value > upper) {
return upper;
}
return value;
}
Firstly, let’s replace the int‘s with the placeholder type.
T clamp(T value, T lower, T upper)
{
if (value < lower) {
return lower;
}
if (value > upper) {
return upper;
}
return value;
}
The compiler has no idea how to interpret T.
We must use a template parameter declaration to inform it that it’s a template type parameter.
template <typename T> // Template parameter declaration
T clamp(T value, T lower, T upper)
{
if (value < lower) {
return lower;
}
if (value > upper) {
return upper;
}
return value;
}
template tells the compiler we’re creating a template.
The template parameters are inserted inside the < >.
The function body is agnostic to the type that the function receives.
When the template is used with int, T becomes int.
When it is used with double, T becomes double.
NOTE: The scope of a template parameter declaration is limited to a single function template. Thus, each function template or class template needs its own template parameter declaration.
That’s it. We’re done! We can now invoke the template.
Using a function template
Function templates have one job: To generate functions
// Syntax
name_of_function_template<desired_type>(arg1, arg2);
We call the function as normal except we include the template argument in < >. The compiler uses this to override the template parameter.
#include <iostream>
template <typename T>
T clamp(T value, T lower, T upper)
{
if (value < lower) {
return lower;
}
if (value > upper) {
return upper;
}
return value;
}
int main()
{
std::cout << clamp<int>(120, 0, 100) << '\n';
std::cout << clamp<double>(-19.2, 0.2, 1.0) << '\n';
}
// Output
100
0.2
The process of creating functions from templates is called function template instantiation.
A function that is instantiated from a template is called a specialization.
The process for instantiating a function is simple: the compiler essentially clones the primary template and replaces the template type (T) with the actual type we’ve specified (int).
Template Argument Deduction
Was typing the template argument tedious? No?
Well, C++ allows us to omit it and really on the compiler to deduce the type..
This works:
clamp(120, 0, 100);
The compiler sees that all three arguments are int, so it deduces:
T = int
This also works:
clamp(-19.2, 0.2, 1.0);
The compiler sees that all three arguments are double, so it deduces:
T = double
The template parameters must be the same
NOTE: The template argument must be a single type. clamp(10, 0.0, 5.0); throws a compiler error
note: deduced conflicting types for parameter 'T' ('int' and 'double')
21 | std::cout << clamp(10, 0.0, 5.0);
A simple fix is to make the arguments the same type:
clamp(10.0, 0.0, 100.0);
or specify the type explicitly:
clamp<double>(10, 0.0, 100.0);
Summary
- A function template is a pattern for generating functions.
- Template parameters act as placeholder types.
- The compiler instantiates concrete functions when the template is used.
The compiler substitutes T with the actual type when the function is called.
Use a template when the same algorithm applies to multiple types:
template <typename T>
T clamp(T value, T lower, T upper);