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

mat*_*scp 8 c++ templates variadic-functions variadic-templates c++11

以下代码编译并运行正常.

void foo() {

}

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

// inside main()
foo(1,1,1);
Run Code Online (Sandbox Code Playgroud)

这个其他代码不编译:

void foo() {

}

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

// inside main()
foo(1,1,1);
Run Code Online (Sandbox Code Playgroud)

编译器说调用没有匹配函数,foo(1,1,1)并且说它foo(Args... args, T x)是候选函数,但模板参数推导/替换失败,因为候选者需要1个参数,但提供了3个参数.

这种情况是否存在任何编译器无法处理的歧义?这个编译错误对我来说似乎不合逻辑.也许这与C++标准有意无关?

m.s*_*.s. 6

Clang的错误信息中有趣的部分是:

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

void foo(Args... args, T x) {
     ^
Run Code Online (Sandbox Code Playgroud)

的问题是,所述参数包Args...发生之前T.

Args...是"贪婪的",所以没有任何参数留给编译器推断T,因此它失败了.

引用标准(强调我的):

[temp.param]/11

函数模板的模板参数包不能跟随 另一个模板参数,除非该模板参数可以从函数模板的参数类型列表中推导出来或者具有默认参数.[例:

...
// 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
Run Code Online (Sandbox Code Playgroud)

- 结束例子]

  • 我不得不投票,因为这是错误的.实际上,"Args ......"并没有贪婪地消费论据.由于它不在最后,它不使用参数,并且大小为"0"(未推断出的参数包将回落到零大小).这意味着`T`变为`int`,其余两个参数没有匹配的参数用于演绎.错误消息"因为候选人需要1个参数,但提供了3个参数." 是正确的和正确的.然而,Clang的错误消息不正确. (6认同)
  • @songyuanyao它是在14.8.1p3.基本上,参数包不能无法推断.它们是"默认"为空.当然,模板参数包"typename ... Args"不是"尾随"(IMO将*template*参数包限制为尾随没有意义,而不是*function*参数包虽然..如果你问我,那就是奇怪的限制.目的是按照所述方式工作,参见http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_defects.html#1399和http://www.open-std.org/jtc1/ SC22/WG21 /文档/ cwg_defects.html#1388 (2认同)

son*_*yao 6

(这个答案基于@ JohannesSchaub-litb的评论)

根据该标准,如果模板参数包用于不在参数列表末尾的函数参数包中,则该参数包不可推导.

§14.8.2.1/ 1从函数调用中减去模板参数[temp.deduct.call]:

当函数参数包出现在非推导的上下文([temp.deduct.type])中时,永远不会推导出该参数包的类型.[例如:

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
}
Run Code Online (Sandbox Code Playgroud)

- 结束例子]

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

函数参数包,不会出现在参数声明列表的末尾.

因此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);
}
Run Code Online (Sandbox Code Playgroud)

这里有一些额外的信息.


[1]我无法在标准中找到关于此的直接表达.最贴近一个是,"A尾随模板参数包([temp.variadic])没有另外推导出将被推断为模板参数空序列".