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

时间:2013-06-24 14:05:48

标签: c++ c++11 variadic-templates

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

和clang说:

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的类型参数参数包吗?

2 个答案:

答案 0 :(得分:12)

正在发生的是模板参数包class... Ts的模板函数和foo<Ts...>的参数类型(P)正在根据{{1}的参数类型(A)推导出来。 }。

14.8.2.5/9说到这一点:

  

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

因此int应推断为一个元素列表class... Ts,因此函数模板应使用参数类型int进行实例化,并且可行。

这是编译器错误。您的代码格式正确。

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

const foo<int>&

但至少在gcc 4.7.2上用类似的方式失败:

template<class A, class... B> struct S { };

template<class... C> void f(S<C...>) { }

int main() { f(S<int>()); }

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具有不完整类型的进一步垃圾。

答案 1 :(得分: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都无法通过模仿一般情况下的解决方法解决这个多年前的错误,我不知道。编译器供应商可能面临性能和正确性之间的不幸选择。