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

僤鯓⒐⒋嵵緔 提交于 2019-12-12 07:46:00

问题


I have a class templated on a type parameter and parameter pack, and am confused about type-deduction of this type; while writing an output-streaming operator I discovered a parameter pack on operator<< will not match both the type and pack parameters for the template class:

#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>();
}

This fails to compile on both gcc-4.7.2 and clang-3.0, so I guess I'm misunderstanding the rules here.

gcc says (where line 16 is the output stream call):

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

and clang says:

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...>& )
                  ^

Could someone please enlighten me as to why the parameter pack for operator<< cannot be deduced to be the type parameter and parameter pack for foo?


回答1:


What is happening is that a template function with a template parameter pack class... Ts, and a parameter type (P) of foo<Ts...> is being deduced against an argument type (A) of foo<int>.

14.8.2.5/9 says of this:

If P has a form that contains <T> or <i> [it does], then each argument Pi [Ts...] of the respective template argument list P is compared with the corresponding argument Ai [int] of the corresponding template argument list of A. If the template argument list of P contains a pack expansion that is not the last template argument, the entire template argument list is a non-deduced context. [the pack expansion is last, so the previous doesnt apply] If Pi is a pack expansion [Ts..., it is], then the pattern of Pi is compared with each remaining argument in the template argument list of A (int). Each comparison deduces template arguments for subsequent positions in the template parameter packs expanded by Pi.

So class... Ts should be deduced as the one element list int, and consequently the function template should be instantiated with the parameter type const foo<int>&, and be viable.

It is a compiler bug. Your code is well-formed.

More succinctly this is well-formed:

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

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

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

but fails similarly on at least gcc 4.7.2 with:

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

C is incorrectly deduced as C = {int, C} (a nonsensical recursion) instead of C = {int}. The broken deduction of C leads to further garbage that S<int, C> has an incomplete type.




回答2:


Wow, I would have thought this was fixed already, but it still doesn't work in prerelease GCC 4.9 and Clang 3.4 builds (courtesy Coliru).

The workaround is simple: use partial specialization to deduce the template arguments elsewhere.

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;
}

Why both GCC and Clang can't solve this years-old bug by imitating the workaround in the general case, I don't know. The compiler vendors are perhaps facing an unfortunate choice between performance and correctness.



来源:https://stackoverflow.com/questions/17277487/why-wont-template-parameter-pack-be-deduced-to-multiple-type-arguments-in-funct

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!