Consider following code, I don\'t understand why empty function of print must be defined.
#include
using namespace std;
void print()
{
}
Recursion is the most general way to program variadic templates, but it's far from the only way. For simple use cases like this, doing a pack expansion directly within a braced initializer list is shorter and likely faster to compile.
template <typename... Types>
void print (const Types&... args)
{
using expander = int[];
(void) expander { 0, (void(cout << args << endl), 0) ...};
}
In C++17, we'll be able to use a fold expression:
template <typename... Types>
void print (const Types&... args)
{
(void(cout << args << endl) , ...);
}
If you don't have empty print
function, imagine a call with 2 parameters :
oups, print()
doesn't exists, because only print
with at least one parameter exists ! So you need a print
without parameters.
print
without any parameters is your final call
To add on to the other answers, I would like to show what the compiler has to generate for the template calls.
nm -g -C ./a.out
(non-optimized build) gives:
void print<char [5]>(char const (&) [5])
void print<char [5]>(char const (&) [5])
void print<char [6], int, int, char, char [5]>(char const (&) [6], int const&, int const&, char const&, char const (&) [5])
void print<char [6], int, int, char, char [5]>(char const (&) [6], int const&, int const&, char const&, char const (&) [5])
void print<char, char [5]>(char const&, char const (&) [5])
void print<char, char [5]>(char const&, char const (&) [5])
void print<int, char [6], int, int, char, char [5]>(int const&, char const (&) [6], int const&, int const&, char const&, char const (&) [5])
void print<int, char, char [5]>(int const&, char const&, char const (&) [5])
void print<int, int, char, char [5]>(int const&, int const&, char const&, char const (&) [5])
void print<int, char [6], int, int, char, char [5]>(int const&, char const (&) [6], int const&, int const&, char const&, char const (&) [5])
void print<int, char, char [5]>(int const&, char const&, char const (&) [5])
void print<int, int, char, char [5]>(int const&, int const&, char const&, char const (&) [5])
print()
You can see all the instantiations of the print
function. The final function that ultimately calls print()
is void print<char [5]>(char const (&) [5])>
You can see that when passed an empty parameter pack, the template parameter list must necessarily be empty. Therefore it just calls print()
. If you explicitly specified the template parameters, like print<T, Args...>(t, args...)
you would get infinite recursion.
Because (using a very "simple" level of explanation) the variadic templates mechanism works like a recursion (it is NOT recursion but this is the simplest way to comprehend it), which "consumes" the variadic parameter list, so you will have to define a "stop" function, at which it will recur in the last step of recursion when the "consumed" parameter list is "empty". This was the explanation that I found the easiest to understand this pretty complex notion.
At the very first step (in main
) you will get a function which has the parameters: (int, const char*, int, int, char, const char*)
Then the variadic parameters are slowly being processed in the variadic function itself, leaving you in the second step with (const char*, int, int, char, const char*)
then (int, int, char, const char*)
and so on ... till you reach the last element (const char*)
, and when this is also processed in the next step you end up with ()
, and the compiler needs this function as a "terminator"
(Yes, this is very non technical and sounds like grandpa frog telling a story to little froglings ...)
CORRECT WAY:
Variadic templates is strictly related to induction
, a mathematical concept.
The compiler resolves the following function call
print('a', 3, 4.0f);
into
std::cout<< 'a' <<std::endl;
print(3, 4.0f);
which is resolved into
std::cout<< 'a' <<std::endl;
std::cout<< 3 <<std::endl;
print( 4.0f);
which is resolved into
std::cout<< 'a' <<std::endl;
std::cout<< 3 <<std::endl;
std::cout<< 4.0f <<std::endl;
print();
At this point it searches for a function overload whose match is the empty function.
The culprit is that you must have, for every possible combination of parameters, only 1 function.
ERROR 1:
Doing the following would be an error
template< typename T>
void print( const T& arg) // first version
{
cout<< arg<<endl;
}
template <typename T, typename... Types>
void print (const T& firstArg, const Types&... args) // second version
{
cout << firstArg << endl; // print first argument
print(args...); // call print() for remaining arguments
}
Because when you call print
the compiler doesn't know which function to call.
Does print(3)
refers to "first" or "second" version? Both would be valid because the first has 1 parameter, and the second can accept one parameter too.
print(3); // error, ambiguous, which one you want to call, the 1st or the 2nd?
ERROR 2:
The following would be an error anyway
// No empty function
template <typename T, typename... Types>
void print (const T& firstArg, const Types&... args)
{
cout << firstArg << endl; // print first argument
print(args...); // call print() for remaining arguments
}
In fact, if you use it alone without the compiler would do
print('k', 0, 6.5);
which is resolved into
std::cout<<'k'<<std::endl;
print(0, 6.5);
which is resolved into
std::cout<<'k'<<std::endl;
std::cout<< 0 <<std::endl;
print( 6.5);
which is resolved into
std::cout<<'k'<<std::endl;
std::cout<< 0 <<std::endl;
std::cout<< 6.5 <<std::endl;
print(); //Oops error, no function 'print' to call with no arguments
As you see in the last attempt, the compiler tries to call print()
with no arguments. However if such a function does not exists it is not called, and that's why you should provide that empty function (don't worry, the compiler will optimize code so empty functions don't decrease performance).