20
votes

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?

2
It probably has something to do with the fact that your class has a single parameter and a parameter pack, whereas the function only has a parameter pack. It wouldn't work with nothing in the pack anyway, so you might as well have the separate parameter.chris
Is that the full error message from gcc? I get "sorry, unimplemented: cannot expand 'Ts ...' into a fixed-length argument list" firstdoctorlove
Upgrade. Gcc 4.8 and Clang 3.3 are out.Potatoswatter
@doctorlove, that "sorry, unimplemented" is GCC 4.6, it's fixed in 4.7Jonathan Wakely

2 Answers

12
votes

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.

1
votes

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.