Overview
Teaching: 15 min
Exercises: 15 minQuestions
Objectives
Overloaded functions may have the same definition. For example:
// overloaded functions
#include <iostream>
using namespace std;
int sum(int a, int b)
{
return a + b;
}
double sum(double a, double b)
{
return a + b;
}
int main()
{
cout << sum(10, 20) << endl;
cout << sum(1.0, 1.5) << endl;
return 0;
}
With corresponding output:
30
2.5
Here, sum
is overloaded with different parameter types, but with exactly the same body.
The function sum
could be overloaded for a lot of types, and it could make sense for all of them to have the same body.
For cases such as this, C++ has the ability to define functions with generic types, known as function templates.
Defining a function template follows the same syntax as a regular function, except that it is preceded by the template
keyword and a series of template parameters enclosed in angle-brackets <>
as follows:
template <template-parameters> function-declaration
The template parameters are a series of parameters separated by commas. These parameters can be generic template types by s
pecifying either the class
or typename
keyword followed by an identifier. It makes no difference whether the generic type
is specified with keyword class
or keyword typename
in the template argument list. The identifier can then be used in the
function declaration as if it was a regular type.
For example, a generic sum
function could be defined as:
template <typename SomeType>
SomeType sum(SomeType a, SomeType b)
{
return a + b;
}
In the code above, declaring SomeType
this way allows it to be used anywhere in the function definition, just as if it was any other type.
It can be used as the type for parameters, as return type, or to declare new variables of this type. In all cases, it represents a generic
type that will be determined on the moment the template is instantiated.
Instantiating a template is applying the template to create a function using particular types or values for its template parameters. This is done by calling the function template, with the same syntax as calling a regular function, but specifying the template arguments enclosed in angle brackets as follows:
name <template-arguments> (function-arguments)
For example, the sum
function template defined above can be called with:
x = sum<int>(10,20);
The function sum<int>
is just one of the possible instantiations of function template sum. In this case, by using int
as template argument
in the call, the compiler automatically instantiates a version of sum
where each occurrence of SomeTyp
e is replaced by int
, as if it
was defined as:
int sum(int a, int b)
{
return a + b;
}
Let’s see an actual example:
// function template
#include <iostream>
using namespace std;
template <class T>
T sum(T a, T b)
{
T result;
result = a + b;
return result;
}
int main() {
int i=5, j=6, k;
double f=2.0, g=0.5, h;
k=sum<int>(i,j);
h=sum<double>(f,g);
cout << k << endl;
cout << h << endl;
return 0;
}
Running this code results in:
11
2.5
In this case, we have used T
as the template parameter name, instead of SomeType
. It makes no difference, and T
is actually a quite
common template parameter name for generic types.
In the example above, we used the function template sum
twice. The first time with arguments of type int
, and the second one with
arguments of type double
. The compiler has instantiated and then called each time the appropriate version of the function.
Note also how T
is also used to declare a local variable of that (generic) type within sum:
T result;
This causes result
to be a variable of the same type as the parameters a
and b
, and the type returned by the function.
In this specific case where the generic type T
is used as a parameter for sum
, the compiler is even able to deduce the
data type automatically without having to explicitly specify it within angle brackets. Therefore, instead of explicitly
specifying the template arguments with:
k = sum<int>(i,j);
h = sum<double>(f,g);
It is possible to instead simply write them without the type enclosed in angle brackets:
k = sum(i,j);
h = sum(f,g);
Naturally, for this to work, the use of types must be unambiguous. If sum
is called with arguments of different types, the compiler
may not be able to deduce the type of T
automatically.
Templates are a powerful and versatile feature. They are not restricted to a single template parameter, and the function can still use regular non-templated types. For example:
// function templates
#include <iostream>
using namespace std;
template <class T, class U>
bool are_equal(T a, U b)
{
return a == b;
}
int main()
{
if (are_equal(10, 10.0))
cout << "x and y are equal" << endl;
else
cout << "x and y are not equal" << endl;
return 0;
}
Running this code will produce:
x and y are equal
Note that this example uses automatic template parameter deduction in the call to are_equal
:
are_equal(10, 10.0)
This call is equivalent to:
are_equal<int, double>(10, 10.0)
In this case there is no ambiguity possible because numerical literals are always of a specific type. Unless otherwise specified with a suffix,
integer literals always produce values of type int
, and floating-point literals always produce values of type double
.
The template parameters can not only include types introduced by class
or typename
keywords, but can also include expressions of a
particular type:
// template arguments
#include <iostream>
using namespace std;
template <class T, int N>
T fixed_multiply(T val)
{
return val * N;
}
int main() {
std::cout << fixed_multiply<int, 2>(10) << endl;
std::cout << fixed_multiply<int, 3>(10) << endl;
}
The output from this program is:
20
30
The second argument of the fixed_multiply
function template is of type int
. It just looks like a regular function parameter, and can
actually be used just like one. However, the value of template parameters is determined at compile time to generate a different instantiation
of the function fixed_multiply
. This means that the value of that argument is never passed at run time. The two calls to fixed_multiply
in main essentially call two versions of the function: one that always multiplies by two, and one that always multiplies by three.
Because of this, template arguments used in this way must always be a constant expression and cannot be passed as a variable.
Key Points