12

The following code compiles and runs ok.

void foo() {

}

template <typename T, typename... Args>
void foo(T x, Args... args) {
  cout << x << endl;
  foo(args...);
}

// inside main()
foo(1,1,1);

This other code does not compile:

void foo() {

}

template <typename... Args, typename T>
void foo(Args... args, T x) {
  foo(args...);
  cout << x << endl;
}

// inside main()
foo(1,1,1);

The compiler says that there is no matching function for call to foo(1,1,1) and says that foo(Args... args, T x) is a candidate, but template argument deduction/substitution failed, because candidate expects 1 argument, but 3 were provided.

Is there any ambiguity with this situation that no compiler can handle? This compile error just seems illogical to me. Maybe this is not in accordance, on purpose, with the C++ standard?

songyuanyao
  • 169,198
  • 16
  • 310
  • 405
matheuscscp
  • 827
  • 7
  • 23

2 Answers2

9

(This answer is based on @JohannesSchaub-litb's comments)

According to the standard, template parameter pack is not deducible if it is used in a function parameter pack not at the end of the parameter list.

§14.8.2.1/1 Deducing template arguments from a function call [temp.deduct.call]:

When a function parameter pack appears in a non-deduced context ([temp.deduct.type]), the type of that parameter pack is never deduced. [ Example:

template<class T1, class ... Types> void g1(Types ..., T1);

void h(int x, float& y) {
  const int z = x;
  g1(x, y, z);                 // error: Types is not deduced
  g1<int, int, int>(x, y, z);  // OK, no deduction occurs
}

— end example ]

And about non-deduced context, §14.8.2.5/5 Deducing template arguments from a type [temp.deduct.type]:

A function parameter pack that does not occur at the end of the parameter-declaration-list.

So the direct reason of foo(1,1,1); failed is that the template parameter Args is not deduced, which is necessary to make the function invocation valid.

To explain the error message, a template parameter pack not deduced will be deduced to an empty sequence of template arguments[1], it means it'll be omitted. Then foo(1,1,1); failed because the number of arguments doesn't match, that's what compiler complained.

Just as the example from standard shown, you could specify the template argument explicitly to avoid type deduction, even though it doesn't meet the original intent of your code. Such as:

template <typename T, typename... Args>
void foo(Args... args, T x) {
}

int main() {
    // inside main()
    foo<int, int, int>(1, 1, 1);
}

Here're some additional informations.


[1] I can't find direct expression about this in the standard. The most close one is this, "A trailing template parameter pack ([temp.variadic]) not otherwise deduced will be deduced to an empty sequence of template arguments."

songyuanyao
  • 169,198
  • 16
  • 310
  • 405
6

The interesting part from Clang's error message is:

main.cpp:11:6: note: candidate template ignored: couldn't infer template argument 'T'

void foo(Args... args, T x) {
     ^

The problem is that the parameter pack Args... occurs prior to T.

Args... is "greedy" and so no parameters are left for the compiler to deduce T, hence it fails.

Quoting the standard (emphasis mine):

[temp.param]/11

A template parameter pack of a function template shall not be followed by another template parameter unless that template parameter can be deduced from the parameter-type-list of the function template or has a default argument. [Example:

...
// U can be neither deduced from the parameter-type-list nor specified
template<class... T, class... U> void f() { } // error
template<class... T, class U> void g() { } // error

— end example]

m.s.
  • 16,063
  • 7
  • 53
  • 88
  • So this is just a "compilers not implementing the standard correctly" situation? – matheuscscp Jul 28 '16 at 07:29
  • @matheuscscp, No, the standard doesn't permit it either. – chris Jul 28 '16 at 07:30
  • @chris is there a specific reason? – matheuscscp Jul 28 '16 at 07:32
  • @matheuscscp Actual wording in **[temp.param]/11**: _If a template-parameter of a primary class template or alias template is **a template parameter pack, it shall be the last template-parameter**. A template **parameter pack of a function template shall not be followed by another template parameter** unless that template parameter can be deduced from the parameter-type-list of the function template or has a default argument_ – Revolver_Ocelot Jul 28 '16 at 07:33
  • @matheuscscp, I don't know of any specific motivation, but it can get complicated pretty quickly and variadic templates are complicated enough as it is. – chris Jul 28 '16 at 07:36
  • Yeah, I can see it... Actually, I was hoping for a "wait for it in C++ 17, dude!", or something like that... Thanks you all, anyway! – matheuscscp Jul 28 '16 at 07:38
  • @Revolver_Ocelot thanks for the hint, I edited that quote in my answer – m.s. Jul 28 '16 at 07:39
  • I'm prepared to be wrong here, but I think Clang's error message is a bit misleading. It's actually the parameter pack which can't be deduced because of `[temp.deduct.type]/5.7`and the last sentence in `[temp.deduct.call]/1`. – TartanLlama Jul 28 '16 at 07:41
  • 6
    I had to downvote this because it's wrong. Actually, `Args...` does not greedily consume arguments. Since it's not at the end, it does not consume arguments and will be of size `0` (parameter packs that have not been deduced will fall-back to size zero). This means that `T` becomes `int` and the remaining two arguments have no matching parameter for deduction. The error message "because candidate expects 1 argument, but 3 were provided." is spot-on and correct. Clang's error message however is incorrect. – Johannes Schaub - litb Jul 28 '16 at 07:52
  • If you either write `void foo(Args... args, T x, T y, T z)` or if you change the call to `foo(1, 1, 1)` it becomes valid code. – Johannes Schaub - litb Jul 28 '16 at 07:56
  • @JohannesSchaub-litb So in this case parameter pack will always be omitted (fall-back to size zero)? Which seems nonsense (to use it in this way). I tried it with GCC and works as you said, but Clang still can't work. BTW: Do you have any quotes about "parameter packs that have not been deduced will fall-back to size zero"? I can't find any at cppreference.com or the standard... – songyuanyao Jul 28 '16 at 09:05
  • 2
    @songyuanyao it's in 14.8.1p3. Basically, parameter packs cannot fail to deduce. they are empty "by default". Granted, the template parameter pack "typename... Args" is not "trailing" (IMO it doesn't really make sense to restrict the *template* parameter pack to be trailing, rather than the *function* parameter pack though.. weird restriction if you ask me). The intent is that this works as described, see http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_defects.html#1399 and http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_defects.html#1388 – Johannes Schaub - litb Jul 28 '16 at 09:38