Hello again!
In this video we are going to look at template specialization. Templates are generic, so we get the same
behaviour when we instantiate our class template or function template, regardless of which types we
use to instantiate it with.
And usually that is what we want.
Sometimes, though, we may want to get different behaviour for certain types. For example, we
may want to be able to handle some types differently.
The Vector Class in the C++ library is a class template. It has an array whose elements are the type
of the template parameter. Unless it is a vector of bool, in which case it is implemented as a bitmap. Because
someone, a long time ago, thought that would be an interesting way to do things.
Another possibility is that the code does not behave correctly for some types.
If we have a plus operator and the arguments are both library strings, then that will behave correctly.
If they are both C strings, then that will not work properly. And we need to do something else if
we have C-style strings.
And also, we may want to be able to optimize the code for certain types.
If you remember the list class, then calling generic algorithms for the list is very inefficient.
It is much faster to call the member functions instead.
So you want to have some code which calls the list member functions, instead of the standard library
generic algorithms.
And we can do all that, by using a template specialization.
So here is how the vector class could be implemented.
We have a generic vector class. Template, angle bracket, type name or class "T". That is the template parameter.
And then the class will implement a vector, using an array, whose elements are this parameter type "T".
For the bool type, we have a specialization.
The syntax is a bit different.
We leave these angle brackets empty, and instead we put the parameter after the name of the class.
So this is a vector whose elements are bool. And then in the body, we would implement that using a bitmap.
You will notice that I have written the specialization immediately after the generic definition, and there
is a reason for that.
And that is how you should normally write things.
If you put the specialization before the generic template, then it will not compile. Because the compiler
needs to know about the generic template.
If the generic template is not visible, then the specialization will not compile, again.
If you are instantiating the template, and the specialization is not visible, and the generic template is
visible, then the compiler will use the generic template.
So the simplest way to make things work is to write the specialization immediately after the generic
version.
How does the compiler know which version to use?
When the compiler instantiates a template, it will look at all the alternatives which match, and then
it will choose the one which is the most specific.
If we have this, then the compiler has a choice between the specialization for bool and the generic
version where the parameter is equal to bool. And the specialization is more specific than the generic
version.
So the compiler will instantiate the bool specialization.
Let's try that out.
So here is a Vector class, with capital 'v', to avoid any confusion.
There is the specialization with the empty angle brackets and the parameter
after the name of the class.
These both have identify() member functions, so we know which version has been instantiated.
And then, in the main function, we instantiate a Vector of ints and a Vector of bool.
The Vector of int will be instantiated using the generic template, because that is the only one which
matches. The Vector of bool will be instantiated using the bool specialization, because that is a better match
than the generic version.
So let's see what we get when we call the identify() functions.
So as we expected, the Vector of has been instantiated using the generic Vector, and the Vector of bool
has been instantiated using the specialization.
Let's try this now with functions.
We have a generic function which takes a container, and calls the standard library
reverse() algorithm.
This will work very well with the containers in the library, except for the standard list.
For the list, we want to call the member function, because that will be much more efficient.
So we write a specialization, for the list.
Now we want this to work for any type of list, not just a list of ints or a list of strings.
And the type of the list depends on the type of the element.
So we need to write a template function which takes the type of the element as the parameter.
And then we can have a list of this generic element type as the argument.
So this will call the reverse() for any list, a list which has any type as arguments.
And then we create a vector. We call the reverse() function, which should call the generic version.
And then we call it again with a list, which should call the specialization for the list.
Okay.
So this works very well if we have a single type that we want to use for the parameter.
If we have different types, then we have to write a different specialization for each type.
But if those types are related in some way, then there is a shortcut.
We can use a partial specialization.
For example, if you want to have a specialization for parameters which are pointers, then we can do
that.
This is only allowed for class and value templates, not for function templates.
And there are some limitations on the arguments, which I will not go into.
So with our Vector class, again, we have the generic Vector. And then we have a partial specialization,
for Vectors whose elements are pointers.
So we still have the "typename T" or the "class T" in angle brackets here, but we also have the specialized
type after the class name.
So this is halfway between the generic and the specialized version.
Then if we have a Vector whose
elements are pointers, then this partial specialization will be instantiated. And for non-pointers, the
generic template will be instantiated.
So we have the same Vector class again, the same generic template. And now we have a partial specialization
for pointers.
So we still have the type parameter, but this can take any kind of pointer. And then, this will be instantiated
with a pointer to the type parameter.
And then we create a Vector of int and a Vector of pointers to int, and we call the identify() functions.
So the Vector of int will be instantiated using the the generic version, because that is the best match.
The Vector of pointers will be instantiated using the partial specialization.
Okay, so that is it for this video.
I will see you next time.
Until then, keep coding!