为什么在函数调用中不将模板参数包推导为多个类型参数?

Why won't template parameter pack be deduced to multiple type arguments in function call?

本文关键字:包推导 类型参数 参数 函数调用 为什么      更新时间:2023-10-16

我有一个在类型参数和参数包上模板化的类,我对这种类型的类型推导感到困惑;在编写输出流运算符时,我发现operator<<上的参数包与模板类的类型和包参数都不匹配:

#include <iostream>
template<class T, class... Ts>
struct foo
{ /* ... */ };
template< class... Ts >
std::ostream& operator<<( std::ostream& os, const foo<Ts...>& )
{
  return os << 42;
}

int main()
{
  std::cout << foo<int>();
}

这在gcc-4.7.2和clang-3.0上都无法编译,所以我想我误解了这里的规则。

gcc说(其中第16行是输出流调用):

t.cpp:16:28: error: cannot bind ‘std::ostream {aka std::basic_ostream<char>}’ lvalue to ‘std::basic_ostream<char>&&’
In file included from /usr/lib/gcc/x86_64-redhat-linux/4.7.2/../../../../include/c++/4.7.2/iostream:40:0,
                 from t.cpp:1:
/usr/lib/gcc/x86_64-redhat-linux/4.7.2/../../../../include/c++/4.7.2/ostream:600:5: error:   initializing argument 1 of ‘std::basic_ostream<_CharT, _Traits>& std::operator<<(std::basic_ostream<_CharT, _Traits>&&, const _Tp&) [with _CharT = char; _Traits = std::char_traits<char>; _Tp = foo<int>]’

叮当声说:

t.cpp:16:16: error: invalid operands to binary expression ('ostream' (aka 'basic_ostream<char>') and 'foo<int>')
        std::cout << foo<int>();
        ~~~~~~~~~ ^  ~~~~~~~~~~
[--- snip: lots of non-viable candidates from standard library ---]
t.cpp:8:19: note: candidate template ignored: substitution failure [with Ts = <>]
    std::ostream& operator<<( std::ostream& os, const foo<Ts...>& )
                  ^

有人能告诉我为什么operator<<的参数包不能推断为foo的类型参数参数包吗?

现在发生的是,一个具有模板参数包class... Ts和参数类型(p)foo<Ts...>的模板函数正在根据参数类型(a)foo<int>推导。

14.8.2.5/9这样说:

如果p的形式包含<T><i>[确实如此],则相应模板参数列表的每个参数Pi[Ts...]将P与A的对应模板参数列表的对应参数Ai[int]进行比较。如果P的模板参数列表包含不是最后一个模板参数的包扩展整个模板参数列表是一个非推导上下文。[包扩展是最后一个,所以前面的不适用]如果Pi是包扩展[Ts...,它是],那么Pi的模式与A的模板参数列表中的每个剩余参数进行比较(int)。每次比较推断Pi展开的模板参数包中后续位置的模板参数

因此,class... Ts应该被推导为单元素列表int,因此函数模板应该用参数类型const foo<int>&实例化,并且是可行的。

这是一个编译器错误。您的代码格式良好。

更简洁地说,这是一个良好的形式:

template<class A, class... B> struct S { };
template<class... C> void f(S<C...>) { }
int main() { f(S<int>()); }

但至少在gcc 4.7.2上类似地失败,具有:

 error: parameter 1 of ‘void f(S<C ...>) [with C = {int, C}]’
        has incomplete type ‘S<int, C>’

C被错误地推导为C = {int, C}(一种无意义的递归)而不是C = {int}C的不完整推导导致了S<int, C>具有不完整类型的进一步垃圾。

哇,我本以为这已经修复了,但它在预发布的GCC 4.9和Clang 3.4版本中仍然不起作用(由Coliru提供)。

解决方法很简单:使用部分专业化在其他地方推导模板参数。

template<class... Ts>
struct foo; // unimplemented
template<class T, class... Ts>
struct foo< T, Ts ... > // specialization for at least one argument
{ /* ... */ };
template< class... Ts >
std::ostream& operator<<( std::ostream& os, const foo<Ts...>& )
{
  return os << 42;
}

我不知道为什么GCC和Clang都不能通过模仿一般情况下的变通方法来解决这个多年前的错误。编译器供应商可能面临着在性能和正确性之间的不幸选择。