Hello again! In this video, we are going to have an overview of templates. So we are going to find out
what templates are, what they look like and how to use them.
So first of all, why are templates useful?
What problem are they meant to solve, or at least help us with?
We often run into situations where we have several bits of code, which are basically the same, but
work with different types of data.
So we have a vector of int, vector of double, vector of string and so on.
If we implement these the obvious way, then there would be three classes. One which uses int, one
which uses double and one which uses string.
But all the actual vector functionality would be exactly the same.
And then later on, if we write a class of our own and we decide that we'd like to have a vector of
this class, then we need to implement another the class for vector of "my type".
So that's obviously not a very good way of doing things, and because C++ is strongly typed.
We can't just create a vector of "anything" or a vector where the type is checked at run time, because
C++ does not work like that. What we do have is templates.
And templates allow us to write code that works with any type of data.
So basically, we can write the functionality for the vector and then we can plug in the actual type
when we use it.
And this is known as generic programming.
I do not mean that templates are generic programming, but it is an example of generic programming where
you can use any type.
And then when we actually use this template, we say what type of data we need.
And then the compiler will go through and generate the actual code.
So if, for example, we want a vector of int, the compiler will go to the definition of the
vector class.
It will plug in the int as the data type and then it will generate a class, which is vector of int.
And this is actually its own type.
So this is quite different from generics in Java, where there's only one vector class and the generics
are really there to check that you're using the types correctly.
In C++, you get a different type for every vector class.
So the pilot will generate this code for the class of a vector of int, and then it will insert the source
code for the class definition into the translation unit.
The translation unit is basically a source code file, as it is seen by the compiler.
So that's the .cc or .cpp file, with all the headers included and pre-processed.
So in effect, the compiler is going to paste this new bit of code into its interpretation of the
source code, and then the compiler will go through the code again.
Actually, compilers go through the code several times.
So this is just another phase.
And then when the compiler goes through and actually converts it all to assembly.
Then this is going to be included in the compilation as part of the program code.
So this is known as a "template instance", the vector of int. And the process in which the compiler generates
the code for a vector of int is called an "instantiation".
This is all done automatically. When the compiler sees vector of int, it will go off and generate this code.
Obviously, the compiler must be able to see the full definition of the class or the function body.
It must be in the same translation unit.
And normally that means putting it in one of the headers.
When we write a template, we use a dummy.
So this is to show the compiler what the code will look like. And the technical name for this dummy is
the "template parameter".
So that is like the int in the angle brackets in the vector.
We can have templates which are functions or classes.
So let's look at a function, as it is a bit simpler to write.
We start off by putting the template keyword, so that tells the compiler we are about to declare a
template.
Then in angle brackets, we put the template parameters, we put class and then the name that we are going
to use for this parameter.
The convention is to use T, although you can use anything you like.
Another convention is that the parameter starts with a capital letter. So that makes it stand out from
the rest of the code because normally in C++, we use lower case.
And then we write out the function, except we do not put any concrete types, we just put the template
parameter.
So this is going to be a function of Max, which takes two of these parameter types by reference to
const and returns another one by value.
And then we write the actual function body.
So that is just like a normal function.
If one argument is greater than the other, return the first one.
Otherwise, return the second one.
And then when we call it this function, the compiler will replace this T by the type that is needed
to make the function call work.
So, for example, if we call Max with the arguments 7.1 and 2.6, the compiler will deduce that it
needs to use double, so T will be replaced by double.
And then the compiler will generate that code and add it to the translation unit.
So it is going to write something like this.
Obviously, the compiler does not actually put comments in the source code! But we end up with a function
that takes two doubles and returns another double.
So here is that code.
Here is the definition of the template function. Here is the template with the parameter T.
And then we call the function in main. We can, if we wish, put...
in there, and that will force the compiler to instantiate it with a double.
Or we can just leave that out and let the compiler work it out for itself.
And then the compiler is going to go in here and replace it by double.
So it'll end up with code that looks like this.
And then there we are, seven.
So seven is greater than 2.6, which is nice to know.
The way that you organize your code is quite important when you are using templates. If you are just
using a normal function,
you just need to give the declaration.
So the compiler will need to know the name of the function, the number and types of its arguments,
and also the return type. And the compiler is just going to check
that the types are used correctly when the function is called. When we call a template function,
the compiler must be able to see the full definition, including the function body.
And the reason for that is that the compiler is going to go through the function body and replace the
template parameter or parameters - you can have more than one - with the necessary types.
The easiest way to do this is to write the template definition in the header, as an inline function,
so it automatically gets included and the compiler can see it.
Some programmers prefer to write the templates in a separate file and include that as a separate include file.
So here is a class template.
So again, we have the template keyword, we have the parameters in angle brackets, and then we just
write the class definition using the type parameter where the type of the data would go.
So this is a class that is going to have a member of this type, and its constructor is going to take
an argument of this type and initialize the member from it.
And then to create an instance of this class, we put the type in angle brackets.
We have to do that, at least until C++ 17. With the function, it is optional, with a class, it is compulsory.
So this is going to instantiate the Test class using string as the parameter.
And then there is our constructor call with the argument,
"Hello", and then the compiler is going to go through and generate some class like this.
So it is going to have some strange name, which the compiler knows means a Test with the string parameter
and then the parameter T will be replaced by string.
So the data member is a string and the constructor takes a string argument.
I was saying that before C++ 17, you have to give the type.
And now in C++ 17, that is actually optional.
The compiler can actually deduce the type.
So there was some difficulty, apparently with deducing the type of a constructor call.
So we've had to write vector angle brackets int.
And now, if we have an initializer, the compiler can deduce the type from the initializer.
So if you put vector vec with initial values one two three, then the component will realize this is
a vector of int.
And it will instantiate a vector template with the parameter as int.
And this is known as CTAD, another C++ acronym, which is actually almost meaningful, which is
quite good for a C++ acronym.
So this stands for Constructor Template Argument Deduction.
So it means we can call constructors the same way we call a templated function.
We do not need to give the type of the parameter.
It only works if the declaration has an initializer in it, otherwise the compiler cannot work out
what the type is.
And then the compiler will deduce it.
And this does make many declarations simpler.
So here is a very simple example of that.
So this is how we had to write it out before C++ 17.
We have to say that the type parameter is int. Now with C++ 17,
we can let the compiler do the work for us.
And this requires a C++ 17 compiler, so let's do that.
So project properties,
language standard,
C++ 17.
So let's see if this one.
I think it should.
Oh, I see so you can declare it without a type parameter, but you cannot use a range for loop with
it.
MinGW will help us.
So that is the code I am going to be compiling.
Yep, no problem.
So there we are, vec equals one, two three.
And finally, the typename. I have put class in here, because that is the way it has been done
originally, since templates were added.
(I think that was the ARM) and in C++ 98, there is also a typename keyword, which you can use instead.
So you can use either of these.
The reasoning is that "class" is a bit confusing because you do not necessarily have to have a class as the
template.
So we have typename instead.
But many programmers still continue to use it.
I think I have mostly managed to retrain my fingers by now, but there might be the odd one that slips through!
OK, so that's it for this video.
I'll see you next time, but meanwhile, keep coding!