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
?
A template parameter is a special kind of parameter that can be used to pass a type as argument: just like regular function parameters can be used to pass values to a function, template parameters allow to pass also types to a function.
You cannot give default arguments to the same template parameters in different declarations in the same scope. The compiler will not allow the following example: template<class T = char> class X; template<class T = char> class X { };
Which parameter is legal for non-type template? Explanation: The following are legal for non-type template parameters:integral or enumeration type, Pointer to object or pointer to function, Reference to object or reference to function, Pointer to member.
A non-type template argument provided within a template argument list is an expression whose value can be determined at compile time. Such arguments must be constant expressions, addresses of functions or objects with external linkage, or addresses of static class members.
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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With