Reputation: 723
Consider following code, I don't understand why empty function of print must be defined.
#include <iostream>
using namespace std;
void print()
{
}
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
}
int main()
{
int i=1;
int j=2;
char c = 'T';
print(i,"hello",j,i,c,"word");
}
Upvotes: 13
Views: 5569
Reputation: 8325
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).
Upvotes: 15
Reputation: 137404
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) , ...);
}
Upvotes: 4
Reputation: 3396
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.
Upvotes: 3
Reputation: 35458
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 ...)
Upvotes: 6
Reputation: 3706
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
Upvotes: 10