Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Expansion with variadic templates [duplicate]

What is the difference between the following 3 calls for gun function?

template <class... Ts> void fun(Ts... vs) {   gun(A<Ts...>::hun(vs)...);   gun(A<Ts...>::hun(vs...));   gun(A<Ts>::hun(vs)...); } 

I am interested in an answer that explains the three calls using a specific example.

like image 997
Laura Maftei Avatar asked Nov 05 '14 21:11

Laura Maftei


People also ask

What are Variadic templates C++?

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 is ellipsis in c++?

Ellipsis in C++ allows the function to accept an indeterminate number of arguments. It is also known as the variable argument list. Ellipsis tells the compiler to not check the type and number of parameters the function should accept which allows the user to pass the variable argument list.

What is parameter pack in c++?

Parameter packs (C++11) A parameter pack can be a type of parameter for templates. Unlike previous parameters, which can only bind to a single argument, a parameter pack can pack multiple parameters into a single parameter by placing an ellipsis to the left of the parameter name.


2 Answers

Originally I just literally answered the question, but I wanted to expand this somewhat to provide a more thorough explanation of how what packs are expanded into what. This is how I think about things anyway.

Any pack immediately followed by an ellipses is just expanded in place. So A<Ts...> is equivalent to A<T1, T2, ..., TN> and hun(vs...) is similarly equivalent to hun(v1, v2, ..., vn). Where it gets complicated is when rather than a pack followed by ellipses you get something like ((expr)...). This will get expanded into (expr1, expr2, ..., exprN) where expri refers to the original expression with any pack replaced with the ith version of it. So if you had hun((vs+1)...), that becomes hun(v1+1, v2+1, ..., vn+1). Where it gets more fun is that expr can contain more than one pack (as long as they all have the same size!). This is how we implement the standard perfect forwarding model;

foo(std::forward<Args>(args)...) 

Here expr contains two packs (Args and args are both packs) and the expansion "iterates" over both:

foo(std::forward<Arg1>(arg1), std::forward<Arg2>(arg2), ..., std::forward<ArgN>(argN)); 

That reasoning should make it possible to quickly walk through your cases for, say, what happens when you call foo(1, 2, '3').

The first one, gun(A<Ts...>::hun(vs)...); expands Ts "in place" and then there's an expression to expand for the last ellipses, so this calls:

gun(A<int, int, char>::hun(1),      A<int, int, char>::hun(2),      A<int, int, char>::hun('3')); 

The second one, gun(A<Ts...>::hun(vs...)); expands both packs in place:

gun(A<int, int, char>::hun(1, 2, '3')); 

The third one, gun(A<Ts>::hun(vs)...), expands both packs at the same time:

gun(A<int>::hun(1),      A<int>::hun(2),      A<char>::hun('3')); 

[update] For completeness, gun(A<Ts>::hun(vs...)...) would call:

gun(A<int>::hun(1, 2, '3'),     A<int>::hun(1, 2, '3'),     A<char>::hun(1, 2, '3')); 

Finally, there's one last case to consider where we go overboard on the ellipses:

gun(A<Ts...>::hun(vs...)...); 

This will not compile. We expand both Ts and vs "in place", but then we don't have any packs left to expand for the final ellipses.

like image 126
Barry Avatar answered Nov 05 '22 03:11

Barry


Here's how they expand when Ts is T, U and vs is t, u:

gun(A<Ts...>::hun(vs)...) -> gun(A<T, U>::hun(t), A<T, U>::hun(u)) gun(A<Ts...>::hun(vs...)) -> gun(A<T, U>::hun(t, u)); gun(A<Ts>::hun(vs)...)    -> gun(A<T>::hun(t), A<U>::hun(u)) 

And one more case you didn't cover:

gun(A<Ts>::hun(vs...)...) -> gun(A<T>::hun(t, u), A<U>::hun(t, u)) 

If you run the code below in VS14 you'll get this output:

calling gun(A<Ts...>::hun(vs)...);     struct A<int,double>::hun(double);     struct A<int,double>::hun(int);     gun(struct A<int,double>, struct A<int,double>); calling gun(A<Ts...>::hun(vs...));     struct A<int,double>::hun(int, double);     gun(struct A<int,double>); calling gun(A<Ts>::hun(vs)...);     struct A<double>::hun(double);     struct A<int>::hun(int);     gun(struct A<int>, struct A<double>); calling gun(A<Ts>::hun(vs...)...);     struct A<double>::hun(int, double);     struct A<int>::hun(int, double);     gun(struct A<int>, struct A<double>); 

Code:

#include <iostream> #include <typeinfo>  using namespace std;  void printTypes() {}  template<typename T, typename... Ts> void printTypes(T, Ts... vs) {     cout << typeid(T).name() << (sizeof...(Ts) ? ", " : "");     printTypes(vs...); }  template<typename... Ts> struct A {     template<typename... Us>     static auto hun(Us... vs) {         cout << "    " << typeid(A).name() << "::hun(";         printTypes(vs...);         cout << ");" << endl;         return A{};      } };  template<typename... Ts> void gun(Ts... vs) {     cout << "    gun(";     printTypes(vs...);     cout << ");" << endl; }  template<typename... Ts> void fun(Ts... vs) {   cout << "calling gun(A<Ts...>::hun(vs)...);" << endl;   gun(A<Ts...>::hun(vs)...);   cout << "calling gun(A<Ts...>::hun(vs...));" << endl;   gun(A<Ts...>::hun(vs...));   cout << "calling gun(A<Ts>::hun(vs)...);" << endl;   gun(A<Ts>::hun(vs)...);   cout << "calling gun(A<Ts>::hun(vs...)...);" << endl;   gun(A<Ts>::hun(vs...)...); }  int main() {     fun(1, 2.0); } 
like image 24
mattnewport Avatar answered Nov 05 '22 01:11

mattnewport