10

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?

TartanLlama
  • 63,752
  • 13
  • 157
  • 193
  • 3
    I would say that the first example, the one that compiles, should not be. It's ill-formed. – Sam Varshavchik Aug 08 '16 at 11:01
  • @SamVarshavchik Can you explain why? – TartanLlama Aug 08 '16 at 11:02
  • The template is explicitly instantiated with four parameters. The template expands the same parameters as parameters to the function call. The function call is expected to take four parameters. Passing five actual parameters to a function call that expects four parameters is ill-formed. – Sam Varshavchik Aug 08 '16 at 11:03
  • 1
    @SamVarshavchik The interpretation taken by those three compilers takes the four template arguments as `A` (`int`), and `Bs` (`{int,int,int}`), then `C` is deduced, so it *is* a function expecting five parameters by that logic. I don't know whether that's the correct logic though. – TartanLlama Aug 08 '16 at 11:06
  • That's a fair point. So, it looks like when a parameter pack ends the template declaration, the compiler expects an explicit template parameter list to initialize the entire parameter pack, without any additional parameter deduction. – Sam Varshavchik Aug 08 '16 at 11:11
  • I would doubt first example is compliant without further research. Do you have evidence you can supply? – Yakk - Adam Nevraumont Aug 08 '16 at 12:03
  • @Yakk Unfortunately I have little other than second-hand assurances; see [this](http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_active.html#2105) CWG issue and Johannes Schaub's comments [here](http://stackoverflow.com/questions/38629601/function-template-parameter-pack-not-at-the-end-of-the-parameter-list/38629697#38629697). If it were just a single compiler acting this way then I would be more suspicious. – TartanLlama Aug 08 '16 at 13:15
  • @Yakk I believe that this is okay because the pack is in a non-deduced context, so the last argument is used to deduce the type of the final template parameter. – TartanLlama Aug 08 '16 at 13:36
  • @tartan except how does the compiler know you are completely defining the pack and not just the prefix? – Yakk - Adam Nevraumont Aug 08 '16 at 13:42
  • @Yakk Because any further function arguments you pass can't be deduced for the pack, so there's no other way to specify the arguments, so you must be completely defining it. (if I understand your question correctly) – TartanLlama Aug 08 '16 at 13:49
  • See also [this defect](http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_defects.html#1399). – TartanLlama Aug 08 '16 at 14:03

1 Answers1

2

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.

TartanLlama
  • 63,752
  • 13
  • 157
  • 193
  • Long story short: don't mess with non-trailing variadic template packs. Unless you know what you're doing. Which is a valid statement for everything C++. – rubenvb Aug 09 '16 at 08:33