Reputation: 1493
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
?
Upvotes: 22
Views: 5847
Reputation: 137770
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.
Upvotes: 2
Reputation: 68588
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.
Upvotes: 14