Function templates

Location
  1. Courses

    /

  2. Complete C++ Course

    /

  3. The basics

    /

  4. Function templates

What are function templates?

Templates allow functions to define its arguments or local variables with unspecified (generic) types. When the function is called, the compiler will automatically create a version of the function by replacing the generic types (template types) by the types of the variables/values given to the function as argument. For example, if we define a function with an argument of template type named T and we call that function by giving it a value of type int as argument, a version (overload) of the function with all the occurrences of T replaced by int will be created.

Note that templates are a feature of C++, version C++11.

Defining a templated function

To define a template type for a function, we must write, before its definition and declaration, the keyword template, and then, inside < and >, the keyword class and the name of the template type.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
#include <iostream> // Function that switches the value of its 2 arguments. template<class T> void switchVal(T &a, T &b) { /* The type T will be replaced by the type of the variables given as argument. */ T c = a; a = b; b = c; } int main() { int a = 5, b = 7; switchVal(a, b); std::cout << a << " " << b << std::endl; // "7 5" is printed in the console. float c = 54, d = 13; switchVal(c, d); std::cout << c << " " << d << std::endl; // "13 54" is printed in the console. return 0; }

In the example above, the two following overloads of the templated function switchVal has been automatically created by the compiler:

1
2
3
4
5
6
7
8
9
10
11
12
void switchVal(int &a, int &b) { int c = a; a = b; b = c; } void switchVal(float &a, float &b) { float c = a; a = b; b = c; }

Many template types

A function may use more than one template type. It is done by, in the template definition, adding additional template types inside its <> and separating them with commas.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include <iostream> template<class T, class V> T add(T a, V b) { /* The type T will be replaced by the type of the first variable/value given as argument and the type V will be replaced by the type of the second variable/value given as argument. */ return a+b; } int main() { float a = 43.67; int b = 90; std::cout << add(a, b) << std::endl; return 0; }

In the example above, the templated function add uses two template types that may represent two different types and it returns the value of the addition of its arguments as the type of its first parameter. In the example above, following version of the function add has been created:

float add(float a, int b) { return a+b; }

Explicitly specifying the types

Earlier, we called the templated functions and let the compiler decide the types to use to create the overload of the function depending on the arguments given. We may explicitly state the types to use by writing them, separated by commas, inside <>, following the name of the function when we call it.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <iostream> template<class T> void print(T v) { std::cout << v << std::endl; } int main() { print<int>(7.8); print<const char*>("Null-terminated string."); print<const std::string&>("std::string"); return 0; }

In the example above, the three following versions (overloads) of the function print has been created by the compiler:

1
2
3
4
5
6
7
8
9
10
11
12
void print(int v) { std::cout << v << std::endl; } void print(const char* v) { std::cout << v << std::endl; } void print(const std::string & v) { std::cout << v << std::endl; }

Here is an other example:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <iostream> template<class T, class V> void printSummation(T a, V b) { std::cout << a+b << std::endl; } int main() { printSummation<float, float>(5, 9.2); printSummation<int, int>(2.3, 4.2); return 0; }

In the example above, the two following overloads of printSummation has been created:

void printSummation(float a, float b) { std::cout << a+b << std::endl; } void printSummation(int a, int b) { std::cout << a+b << std::endl; }

Obviously, an error may happen if we give wrong arguments/types to a templated function. From the example above, if we write the following:

printSummation("C style ", "character string.");

An error would happen, because the function overload created would try to add two character strings together, while the addition operation between two char pointers is not defined (Does not exist).

The function call from above would have the effect of creating the following (faulty) overload:

void printSummation(const char * a, const char * b) { std::cout << a+b << std::endl; }

Template values

A templated function may not only use template types, but also template variables. When a function using template variables is called, the template variables are replaced by the values given between <>. The values given must be literals (Not variables). A template variable is defined by writing, between the <> of the template definition, its type followed by its name.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include <iostream> template<unsigned N> void printArraySize() { float array[N]; std::cout << sizeof(array) << std::endl; } int main() { printArraySize<1>(); printArraySize<5>(); printArraySize<10>(); return 0; }

In the example above, the following versions of the function printArraySize are created:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
void printArraySize() { float array[1]; std::cout << sizeof(array) << std::endl; } void printArraySize() { float array[5]; std::cout << sizeof(array) << std::endl; } void printArraySize() { float array[10]; std::cout << sizeof(array) << std::endl; }

Default template types and values

Like the arguments of functions, the template types and values may have default values. This is done the same way as with functions: We add an equal sign =, after the name of the template variable, and then we write the default value.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#include <iostream> // The template variable T has the type 'int' as default type. template<class T = int> T func() { std::cout << T(123.456) << "\n"; } // The template variable V has the value 23 as default value. template<int V = 23> void func2() { std::cout << V << "\n"; } int main() { /* No type explicitly specified: The default one is used, causing precision loss (The decimals of the number are discarded). */ func(); func<float>(); func2(); func2<71>(); return 0; }

Template specialization

A templated function is a generic function that will have the same behavior no matter the types it uses. We may specialize a templated function so when it uses specific types or values, its behavior is different. This is done by writing an empty template definition before the specialized function definition and writing the types and values between <> after the name of the function.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#include <iostream> template<class T, unsigned N> void printArraySize() { T array[N]; std::cout << sizeof(array) << std::endl; } // Specialized templated function template<> void printArraySize<char, 1>() // It is called when the type given is char and value 1. { std::cout << "Single char." << std::endl; } int main() { printArraySize<char, 1>(); printArraySize<int, 1>(); printArraySize<int, 5>(); return 0; }

In the example above, if the type given to the templated function printArraySize is char and the value given is 1, then it uses a special version of the function. Otherwise, the generic one is used.

Here are the 3 versions of printArraySize created in the example above:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
void printArraySize() { std::cout << "Single char." << std::endl; } void printArraySize() { int array[1]; std::cout << sizeof(array) << std::endl; } void printArraySize() { int array[5]; std::cout << sizeof(array) << std::endl; }

Unknown amount of arguments of unknown types

Using templates, it is possible to create functions that can receive any amount of arguments of any types. A template type that can represent a set of arguments is defined by adding, inside the template definition, 3 dots after the keyword class.

template<class ... Args>

Inside the function header, the variable representing the list of arguments must be of the template type that can represent a set of arguments, and there must be 3 dots before its type and its name.

void templFunc(Args ... args)

We can than give the arguments to a function call by putting, inside the parentheses of the function call, the name of the argument representing the list of arguments, followed by 3 dots.

func(args...);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#include <iostream> void func(const char* str) { std::cout << "func(const char* str).\n"; } void func(int a) { std::cout << "func(int a).\n"; } void func(float a, int b) { std::cout << "func(float a, int b).\n"; } template<class ... Args> void templFunc(Args ... args) { func(args...); } int main() { templFunc("String of characters."); templFunc(65); templFunc(93.2f, 89); return 0; }

In the example above, the 3 following overloads of templFunc has been created:

1
2
3
4
5
6
7
8
9
10
11
12
void templFunc(const char* str) { func(str); } void templFunc(int a) { func(a); } void templFunc(float a, int b) { func(a, b); }

The argument list does not have be the only argument of the function, but it must be the last:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#include <iostream> void func(const char* str) { std::cout << str << ".\n"; } void func(int a) { std::cout << a << ".\n"; } void func(float a, int b) { std::cout << a << " " << b << ".\n"; } template<class ... Args> void templFunc(const char* str, Args ... args) { std::cout << str; func(args...); } int main() { templFunc("A string of character: ", "Sky"); templFunc("An integer: ", 65); templFunc("A floating-point number and an integer: ", 93.2f, 89); return 0; }

Right now, it may not be obvious how argument lists can be useful, but we will see, later, that it can be very convenient.