函数模板参数包不在参数列表的末尾

Function template parameter pack not at the end of the parameter list

以下代码编译运行正常

void foo() {

}

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

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

其他代码无法编译:

void foo() {

}

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

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

编译器说没有匹配函数来调用 foo(1,1,1) 并说 foo(Args... args, T x) 是一个候选者,但是模板参数 deduction/substitution 失败了,因为候选者需要 1 个参数,但提供了 3 个。

这种情况有没有任何编译器无法处理的歧义?这个编译错误对我来说似乎不合逻辑。也许这不是故意的,符合 C++ 标准?

Clang's error message 中有趣的部分是:

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

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

问题是参数包 Args...T 之前出现。

Args... 是 "greedy",因此没有参数留给编译器推导 T,因此失败。

引用标准(强调我的):

[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]

(此答案基于

按照标准,模板参数包如果用在不在参数列表末尾的函数参数包中,是不可推导的。

§14.8.2.1/1 从函数调用推导模板参数 [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 ]

关于非推导上下文,§14.8.2.5/5 从类型推导模板参数 [temp.deduct.type]:

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

所以foo(1,1,1);失败的直接原因是没有推导模板参数Args,这是使函数调用有效所必需的。

为了解释错误消息,未推导的模板参数包将被推导为空的模板参数序列[1],这意味着它将被省略。然后 foo(1,1,1); 失败,因为参数的数量不匹配,这就是编译器所抱怨的。

正如标准示例所示,您可以明确指定模板参数以避免类型推导,即使它不符合代码的初衷。如:

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

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

Here一些附加信息。


[1] 我在标准中找不到关于此的直接表达。最接近的是this、"A trailing template parameter pack ([temp.variadic]) not otherwise deduced will be deduced to an empty sequence of template arguments."