Hello again!
In this video, we are going to look at variadic templates.
A variadic function is one which can take any number of arguments. If you have done any C programming, or
you were taught C++ by someone who thinks it is really C with a few extra features, then you will have
probably used the printf() function for doing output.
This takes a const char pointer as its first argument. Which is a string, a C string, containing formatting
information. And then it has a variable number of arguments, containing the data to be displayed.
This is not type-safe, so you can do things like saying you are going to print a C-style string, and then
giving it an integer, which is not a valid pointer.
So your program will crash.
You can also provide the wrong number of arguments, and there is no check on that.
And that is because all this argument processing is done at runtime. And it only works with types
in the C language.
It does not understand objects,
for example. In C++11, we have variadic template functions.
These are variadic functions which are done
at compile time, so they are very efficient. And they are completely type safe.
The syntax for this looks rather complicated, when you see it for the first time.
If you think of a normal template function, we would have the template premises listed explicitly.
So we would have "typename T", comma, "typename U", and so on.
Then we would have the function arguments. So we would have "T t1", "U u1", comma, and so on.
With a variadic function, we can have any number of template parameters and any number of function
arguments, so we cannot do that.
So instead there's this syntax. "typename", with three dots after it, represents a list of template
parameter types.
And then this list, followed by three dots, represents a list of arguments.
And the types of these arguments will match the template parameters.
These lists with three dots are known as "parameter packs".
So we have one parameter pack which represents the template parameters, and one parameter pack which
represents the function arguments.
So how does this work?
When we use a function template, which is not variadic, the compiler will deduce the template parameters,
and then deduce the argument parameters from those.
When we have a variadic function,
the compiler needs to deduce the number of arguments as well.
So if we have this function and we call it with one argument, which is a string. Then the compiler
will realize it needs one template parameter and it needs one argument.
And then this will be instantiated as a function which takes string.
If we call it with three arguments with different types, the compiler will realize that it needs
three different template parameters.
So the template parameter pack will be populated with three different types, int, double and string.
And then the argument pack will be populated with 42, 0 and "text" (string object)
And then this template will be instantiated as function taking int, double and string.
These parameter packs are only available at compile time.
We cannot use them when the program is running.
In fact, there are only three things we can do with a parameter pack.
We can use sizeof, with three dots, and that will give us the number of elements in a pack. We can use
make_tuple() to store the arguments in a tuple object. Which is very useful, because that makes the arguments
available at run-time.
And we can also iterate over the elements, using template recursion. Which we have not covered yet, but
I will cover that later on.
So first of all, let's try out the sizeof operator.
So we're going to call this variadic template function with one argument and three arguments.
Then we are going to print out the result of calling sizeof. And we expect to get 1 for this call
and 3 for this call.
And there we are.
And do not forget to put the dots after the "sizeof"! This will cause the parameter pack to be expanded. Which
means this pack will be replaced by a list of the function arguments, separated by commas.
And then with the tuple. Same function call in main(). We call make_tuple(). We pass the argument pack and
this time we need to put the dots after the argument pack, to expand the argument pack.
So again, this will be replaced by the arguments, separated by commas. And then we can do
"tuple-related" things. So we can call get().
We do not know how many arguments there are, but we can assume there will be at least one.
So it is safe to get the first element.
And the first argument is 42.
Right.
Template
recursion. You have seen, I hope, how you can have recursion with non-template functions.
You have a function which calls itself. And normally, you make some kind of change to the arguments,
As with the famous factorial example, where you decrease the argument by one each time you call it.
And then you have a version, which is when the argument gets down to 1, and you stop recursing. You can
do this with templates.
So we could, for example, have a function which takes three arguments. And then this calls another
version of the function, which takes two arguments. And then that calls another version of the function,
which takes one arguments, and then we have no more arguments.
So the recursion stops.
So this function, could do something with the argument "t", and then call the same function with the
remaining arguments.
And this will do something with the argument "u", and then do something, and then call itself again with
the remaining argument and then this can do something with the argument.
"v". So if you imagine this as a list of three elements, then this function will pop the first element
off the list. And then it will call itself again, pop the second elements of the list. And then pop the
last element of the list.
So that is if we know we have three arguments.
If we don't know how many arguments there are, then we need a variadic template.
And actually, this works slightly differently.
We have one parameter, one argument, which is a single template parameter, a single argument. And then
the rest of the parameters and the arguments are represented by parameter packs.
So if we call this with three arguments, then "t" will be the first argument, and the pack will contain
the second and third arguments.
Then we do something with the first argument.
Then we call it again.
We pass the second and third elements. So, the members of the parameter pack.
Then it gets called again.
The second element will be this argument T, and the parameter stack will contain the third element.
And then we need to have a function with a single argument, which will finish the recursion by processing
the last argument. And, one important detail.
This non-variadic function must be declared before the variadic template.
So here we are, with our variadic template function.
We are going to do something with the single argument.
So this will take a single argument, and then the rest of the arguments in the parameter pack.
So this will pop the first argument off the list, then call itself with the rest of the arguments.
Then the second element will now become this argument
"t". And this pack will contain the remaining elements, from the third element onwards. Then we process
the second element, and so on.
And we have our non-variadic function declared above the variadic one, and this will process the last element.
So that will be the last argument to
the function call.
And then, in main(), we call this with int, double and string arguments.
So the first time through, "T" will be an int. "Args" will be a pack containing double and string.
And this parameter here will be "i".
Then this argument parameter pack will contain "d" and "s".
Then this will process the first argument and call itself again. The next time,
The first argument will be "d", the parameter type will be double, and the pack will be just the
string argument.
And then, finally, this function is called with the string arguments.
So there we are. The first time through, we have a call with three arguments.
It processes the int argument,
42. The next time
through, we have two arguments. It processes the double. And then, finally, it calls the non-variadic template with
the argument "text". And then finishes.
Okay, so that is it for this video.
I will see you next time.
But until then, keep coding!