4.3. Function templates¶
Function overloads allow programmers to reuse function names. Functions with the same name may or may not accomplish the same task.
What if our functions actually are intended to do exactly the same thing, but merely on different types?
Function overloads allow us to write functions with the same names and different parameter lists, but each function still requires its own function body, even if it's only to call another function.
Example
These two overloads each add their parameters, only on different types:
constexpr int sum (int a, int b);
constexpr double sum (double a, double b);
Run It
These two overloads each add their parameters, only on different types:
1#include <iostream>
2
3constexpr int sum (int a, int b) {
4 return a+b;
5}
6
7constexpr double sum (double a, double b) {
8 return a+b;
9}
10
11int main () {
12 std::cout << sum (10,20) << '\n';
13 std::cout << sum (1.0,1.5) << '\n';
14}
C++ provides a way to write a single piece of code that can stand in for an entire class of functions that all do exactly the same thing.
In C++, we can define a template for a function.
The template defines a function generating recipe
using a generic type as a placeholder.
Templates are created using the template keyword,
followed by zero or more template parameters in angle brackets <>.
For example:
template <template-parameters> function-declaration
Note
Template parameters are optional.
It looks strange to have a template with no template parameters,
but it is perfectly legal. For example, as we will explore as part of
hash tables, the overloads of std::hash<> normally do not include
template parameters:
struct point {
int x;
int y;
}
namespace std {
template <>
struct hash<point>
{
// hash function implementation for a point
};
}
Example
Using templates, our previous sum functions collapse down to:
template <class T>
constexpr T sum (T a, T b) {
return a+b;
}
Run It
1#include <iostream>
2
3template <class T>
4constexpr T sum (T a, T b) {
5 return a+b;
6}
7
8int main () {
9 std::cout << sum<int> (10,20) << '\n';
10 std::cout << sum<double> (1.0,1.5) << '\n';
11}
When identifying template parameter types,
it is common to see either typename or class.
As we will see later, a class defines a type,
so for the purposes of a template, they are the same.
Whether you use 'typename' or 'class' is a matter of preference.
The identifier T is traditional,
but any valid variable name could be used.
In introductory template tutorials AnyType is not uncommon.
Templates are normally completely specified in header files. Because templates are not either declarations or definitions, it is an error to write a template in a cpp source file and then try to use it in another source file.
4.3.1. Using templated functions¶
In short, using functions generated by templates is not very different from a non-templated function.
You can explicitly provide the type:
std::cout << sum<int> (10, 20) << '\n';
std::cout << sum<double> (1.0, 1.5) << '\n';
Or let the compiler deduce the type:
std::cout << sum (10, 20) << '\n';
std::cout << sum (1.0, 1.5) << '\n';
Given a template of a single generic type, take care when mixing types when two or more parameters are involved:
1#include <iostream>
2
3template <class T>
4constexpr T sum (const T a, const T b) {
5 return a+b;
6}
7
8int main () {
9 std::cout << sum <double> (10,1.5) << '\n'; // OK
10 std::cout << sum <int> (10,1.5) << '\n'; // Compiles with warning
11 std::cout << sum (10,1.5) << '\n'; // Compile error
12
13 return 0;
14}
The call to sum <double> implicitly converts the int
to double without warning.
The warning for sum <int> happens because we have explicitly
declared the function to take type int,
but the second argument is a double.
The warning says that the copy of 1.5 passed to sum
will be truncated to 1,
which is a narrowing conversion.
The error is due to the compiler not be able to find a function overload that meets the calling requirements. Even though sum is a template, the compiler will say:
no matching function for call to 'sum'
note: candidate template ignored: deduced conflicting types
for parameter 'T' ('int' vs. 'double')
In the third call to sum, we asked the compiler to deduce the types.
Since the template defines a function with a single type
for both arguments and the return value,
it doesn't know which to choose.
Both int and double are equally valid choices.
The examples on lines 9 and 10 are valid because the compiler does not need to deduce the type, it was explicitly told the type of the function to generate.
4.3.2. Multiple template parameters¶
A sum function that only adds numbers of the same type is not particularly useful.
Templates also allow defining multiple types to be used in a template
with each parameter potentially having a different type.
#include <iostream>
template <typename T1, typename T2>
constexpr bool are_equal (const T1& a, const T2& b) {
return (a==b);
}
int main () {
if (are_equal(10, 10.0)) {
std::cout << "x and y are equal\n";
} else {
std::cout << "x and y are not equal\n";
}
}
There is no 'rule' that says each template parameter can be used only once in the function declaration.
template <typename T1, typename T2>
constexpr T2 foo (const T1& x, const T2& y) {
T1 tmp_x = x;
T2 tmp_y = y < 1? 1: y*y;
while (tmp_y < tmp_x) {
++tmp_x;
tmp_y *= 2;
}
T2 result = tmp_y;
return result;
}
4.3.3. Non-type template parameters¶
Not every template parameter has to name a type. A template parameter can also be a compile-time value.
The following example defines a template
that defines a function that multiplies
a value of type T by a provided int N.
The template parameter int N can be used in the function body
just like any other local variable or function parameter.
Non-type template parameters are constant expressions inside the template body.
Example
template <class T, int N>
constexpr T multiply (const T& val) {
return val * N;
}
The multiply template must be called using
template parameters
const double two_pi = multiply<double,2>(3.14159);
Run It
1#include <iostream>
2
3// it is possible to forward declare a template
4template <class T, int N>
5constexpr T multiply (const T& val);
6
7int main() {
8 std::cout << multiply<double,2>(3.14159) << '\n';
9 std::cout << multiply<long,3>(10) << '\n';
10}
11
12// note the definition includes ALL of the declaration
13// including the template information
14template <class T, int N>
15constexpr T multiply (const T& val) {
16 return val * N;
17}
This is exactly what the std::array class template does. As a wrapper around a raw array, when this container is used, the size of the array is a required template parameter:
std::array<int,4> numbers {2, 4, 6, 8};
Note
Templates that include non-type template parameters need values for those
parameters.
The values may be written explicitly or deduced from function arguments.
In this multiply example, the type T can be deduced from the
argument, but there is no argument that tells the compiler which value to use
for N.
template <class T, int N>
constexpr T multiply (const T& val) {
return val * N;
}
int main() {
multiply(3); // compile error: multiply times what?
}
More to Explore
From cppreference.com:
The utility function std::hash