Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Variadic Templates example

Tags:

c++

c++11

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");

}   
like image 458
Adib Avatar asked May 18 '16 11:05

Adib


People also ask

What use are Variadic templates?

Variadic templates are class or function templates, that can take any variable(zero or more) number of arguments. In C++, templates can have a fixed number of parameters only that have to be specified at the time of declaration. However, variadic templates help to overcome this issue.

What are Variadic templates C++?

A variadic template is a class or function template that supports an arbitrary number of arguments. This mechanism is especially useful to C++ library developers: You can apply it to both class templates and function templates, and thereby provide a wide range of type-safe and non-trivial functionality and flexibility.

How do you write a variadic function in C++?

Variadic functions are functions (e.g. std::printf) which take a variable number of arguments. To declare a variadic function, an ellipsis appears after the list of parameters, e.g. int printf(const char* format...);, which may be preceded by an optional comma.

How do Variadic functions work in C?

Function arguments are placed on the stack. So executing sum_squares(2,3) means placing 2 and 3 (amongst some other things) on the stack. Putting these two values on the stack means advancing the stack pointer enough for two int values, placing our two values in the free space.


2 Answers

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) , ...);
}
like image 184
T.C. Avatar answered Sep 27 '22 19:09

T.C.


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.

  • All functions that have 1 or more arguments are matched to the variadic template
  • All functions that have no argument are matched to 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).

like image 26
CoffeDeveloper Avatar answered Sep 27 '22 20:09

CoffeDeveloper