Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Perplexing non-trailing parameter pack behaviour

I've come across some interesting variadic template function behaviour. Can anyone point out the relevant rules in the standard which define this?

GCC, ICC and MSVC compile the following code successfully (Clang doesn't, but I understand that this is due to compiler bugs).

template<class A, class... Bs, class C>
void foo(A, Bs..., C) { }

int main()
{
    foo<int, int, int, int>(1, 2, 3, 4, 5);
}

In this call to foo, template arguments are provided for A and Bs, then C is deduced to be int.

However, if we simply flip the last two template parameters:

template<class A, class C, class... Bs>
void foo(A, Bs..., C) { }

Then all three compilers throw errors. Here is the one from GCC:

main.cpp: In function 'int main()':
main.cpp:8:42: error: no matching function for call to 'foo(int, int, int, int, int)'
     foo<int, int, int, int>(1, 2, 3, 4, 5);
                                          ^
main.cpp:4:6: note: candidate: template<class A, class C, class ... Bs> void foo(A, Bs ..., C)
 void foo(A, Bs..., C) { }
      ^~~
main.cpp:4:6: note:   template argument deduction/substitution failed:
main.cpp:8:42: note:   candidate expects 4 arguments, 5 provided
     foo<int, int, int, int>(1, 2, 3, 4, 5);
                                      ^

To make things more interesting, calling with only four arguments is invalid for the first foo, and valid for the second.

It seems that in the first version of foo, C must be deduced, whereas in the second, C must be explicitly supplied.

What rules in the standard define this behaviour?

like image 961
TartanLlama Avatar asked Aug 08 '16 10:08

TartanLlama


1 Answers

As is often the case, the answer came to me a few hours after I posted the question.

Consider the two versions of foo:

template<class A, class... Bs, class C>
void foo1(A, Bs..., C) { }

template<class A, class C, class... Bs>
void foo2(A, Bs..., C) { }

and the following call (assuming foo is foo1 or foo2):

foo<int,int,int,int>(1,2,3,4,5);

In the case of foo1, the template parameters are picked as follows:

A = int (explicitly provided)
Bs = {int,int,int} (explicitly provided)
C = int (deduced)

But in the case of foo2 they look like this:

A = int (explicitly provided)
C = int (explicitly provided)
Bs = {int,int} (explicitly provided)

Bs is in a non-deduced context ([temp.deduct.type]/5.7), so any further function arguments can not be used to extend the pack. As such, foo2 must have all it's template arguments explicitly provided.

like image 67
TartanLlama Avatar answered Nov 02 '22 19:11

TartanLlama