Template is a great C++ language feature, with which we can make classes and functions operate on generic types without actually writing code for all possible valid types.
For example std::vector
container in C++11
is a template class. We can use it for with different value types, with the following syntax.
std::vector<int> v1;
std::vector<char> v2;
Here, v1
can hold values of type int
and v2 can hold values of type char
.
In this blog we will be discussing basic template usage in C++
. So, let’s get started.
All the examples in this blog will consider function templates
, i.e. functions with template parameters. However most of the ideas can be applied to a class template also. Let`s look at the following example.
[Example 1]
#include <iostream>
#include "PrintHelper.hpp" // is a helper utility header for printing
using namespace std;
template<typename T>
void func(T x) {
PrinF; // prints signature of this function
// do something with x
}
int main() {
func(1); // auto deduce
BreakLine; // prints a newline
func("string"); // auto deduce
}
Let`s view the output of the above program.
8:void func(T) [with T = int]
8:void func(T) [with T = const char*]
Not to confuse, 8
is just the line number here, printed using PrintF
utility function.
In this example, func
is a function templated with type paramter T
, and func
takes an argument of type T
.
From the main function
we can pass any kind of values to the function func
and the type of T
will be deduced.
Lets try to print the type of x
using our print utility functions. (ignore the type_name utility for now. It just prints the required type )
template<typename T>
void func(T x) {
PrinF;
// prints the type of x
std::cout <<"Type(x) : "<< type_name<decltype(x)>() << std::endl;
// prints the type of T
std::cout <<"Type(T) : "<< type_name<T>() << std::endl;
// do something with x
}
Here is the output:
8:void func(T) [with T = int]
Type(x) : int
Type(T) : int
8:void func(T) [with T = const char*]
Type(x) : char const*
Type(T) : char const*
It seems that type of T
and type of x
is same, which is very intuitive also in this particular example. But that might not be always true as we will see below.
Whenever we write func(some_value)
compiler tries to find
Or resolve
the type of T
as well as type of x
. This type resolving task
is also called as template type deduction
. With these rules of template type deduction
the deduction happens in two folds.
step 1. Deduction of T
step 2. Deduction of type of argument (x)
The iteresting thing is that, step 1
depends on how we declare the function arguments.
template<typename T>
void func(T x) {
In our current example, we declare the arguments as T x
. With type deduction rules, the type of x
and T
will be same in this case.
Lets try to change the argument declaration to const T& x
(why const
?? it has to do with lvalue or rvalue binding. we will not discuss that now)
#include <iostream>
#include "PrintHelper.hpp"
using namespace std;
template<typename T>
void func(const T& x) {
PrinF;
std::cout <<"Type(x) : "<< type_name<decltype(x)>() << std::endl;
std::cout <<"Type(T) : "<< type_name<T>() << std::endl;
}
int main() {
func(1); // auto deduce
BreakLine;
func("string"); // auto deduce
}
Output:
8:void func(const T&) [with T = int]
Type(x) : int const&
Type(T) : int
8:void func(const T&) [with T = char [7]]
Type(x) : char [7] const&
Type(T) : char [7]
As we can see that, in the instantiation of func("string")
: T
becomes char [7]
(array of 7 characters) and type of x
becomes char [7] const&
which is reference to an array of const char of length 7
The two fold step becomes evident with rvlaue references
with argument declaration as T&& x
. We will dicusss this in the next blog.
OK. Enought of type deduction. But type deduction rules are very important. To know more about the rules of C++ template type deduction, there is a whole talk on it by Scott Meyers. Here is the link from cppcon 2014.
So, in a non template world, we would have written two different function to handle int
and strings
.
Therefore the basic question is how a single definition of func
handling different types ?
The answer is : "Compiler generates required functions for us"
. So in the above example,
we have two instances of calling func(....)
, one with int
and the other one with const char*
.
Compiler sees this and generates two function definitions for us. We can check this in gdb.
// compile the code
g++ -g -ggdb -std=c++11 p01.cpp -fsanitize=address -fsanitize=leak
// run gdb
gdb ./a.out
>>> info functions func
All functions matching regular expression "func":
File p01.cpp:
void func<char [7]>(char const (&) [7]);
void func<int>(int const&);
Moving on, there can be three different types of template parameters:
template < parameter-list >
declaration
Each parameter in parameter-list may be:
1. a non-type template parameter
2. a type template parameter
3. a template template parameter.
We have only discusseed 2. a type template parameter
in the above examples.
Lets know about 1. a non-type template parameter
A non-type template parameter
is an expression whose value can be determined at compiler time such as constant expression and address of function etc. More on this.
Here is an example: [Example 2]
#include <iostream>
#include "PrintHelper.hpp"
using namespace std;
template<int N>
void func(int indx) {
// determine the size of the buffer using non-type template parameter
static char x[N];
// just doing some opration with the array
if(indx < N && indx >= 0) {
x[indx] = indx*indx;
}
// print the array
for(int i = 0;i<N;i++) {
printf("x[%d] = 0x%x ",i,x[i]);
}
printf("\n");
}
int main() {
func<6>(0);
func<6>(1);
func<6>(2);
func<6>(3);
}
Before viewing the output, lets check the template intantiation syntax in the above example.
func<6>(args ...)
this is one more way specifying the template parameter.
If we consider the earlier example, we could have instantiate func
template function as follows:
func<int>(1);
func<string>(std::string("abc"));
func<some_type>(value_of_that_type)
Back to the example 2, func<6>(args ...)
is going to fix the template parameter N = 6 and the compiler is going to create a character array of size 6.
Here is the output:
x[0] = 0x0 x[1] = 0x0 x[2] = 0x0 x[3] = 0x0 x[4] = 0x0 x[5] = 0x0
x[0] = 0x0 x[1] = 0x1 x[2] = 0x0 x[3] = 0x0 x[4] = 0x0 x[5] = 0x0
x[0] = 0x0 x[1] = 0x1 x[2] = 0x4 x[3] = 0x0 x[4] = 0x0 x[5] = 0x0
x[0] = 0x0 x[1] = 0x1 x[2] = 0x4 x[3] = 0x9 x[4] = 0x0 x[5] = 0x0
GDB output also using info functions
command:
>>> info functions func
All functions matching regular expression "func":
File p02.cpp:
void func<6>(int);
Although this example of non type template argument
does not give much usefulness, but we will see better examples in the upcoming blog.
What next ?
So, in the next blog, we will discuss on template template parameter
(the 3rd type of template parameters) and template specialization
. We will also discuss templated class or structs. Till then why not check this awesome talk on Runtime Polymorphism by Sean Parent.