21

我有一个在类型参数和参数包上模板化的类,并且对这种类型的类型推导感到困惑;在编写输出流操作符时,我发现一个参数包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吗?

4

2 回答 2

13

正在发生的事情是,一个带有模板参数包的模板函数class... Ts和一个参数类型 (P)foo<Ts...>正在根据一个参数类型 (A) 推导出来foo<int>

14.8.2.5/9 这样说:

如果 P 具有包含<T><i>[it does] 的形式,则将相应模板参数列表 P 的每个参数 Pi [ Ts...] 与 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>类型不完整。

于 2013-06-24T14:28:02.750 回答
1

哇,我原以为这已经修复了,但它在预发布的 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无法通过在一般情况下模仿解决方法来解决这个多年前的错误,我不知道。编译器供应商可能面临性能和正确性之间的不幸选择。

于 2013-06-24T23:05:57.187 回答