boycy
boycy

Reputation: 1493

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

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

Answers (2)

Potatoswatter
Potatoswatter

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

Andrew Tomazos
Andrew Tomazos

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

Related Questions