12
votes

Consider the following contrived piece of code:

template <class... > struct pack { };

template <class R, class T, class... Args>
int foo(pack<T>, Args...)
{
    return sizeof(R);
}

template <class R, class T, class... Ts, class... Args>
int foo(pack<T, Ts...>, Args... args)
{
    return foo<T>(pack<Ts...>{}, args...);
}

int main() {
    // gcc: OK, clang: ambiguous
    foo<int>(pack<int>{});

    // gcc: ambiguous, clang: ambiguous
    foo<int>(pack<int>{}, 0);
}

Both gcc and clang accept both calls if the 2nd overload is changed to take a pack of at least 2 types instead of a pack of at least one type:

template <class R, class T, class T2, class... Ts, class... Args>
int foo(pack<T, T2, Ts...>, Args... args)
{
    return foo<T>(pack<T2, Ts...>{}, args...);
}

If the non-deduced template parameter is moved to a deduced template parameter, then:

template <class... > struct pack { };

template <class R, class T, class... Args>
int foo(pack<R>, pack<T>, Args...)
{
    return sizeof(R);
}

template <class R, class T, class... Ts, class... Args>
int foo(pack<R>, pack<T, Ts...>, Args... args)
{
    return foo(pack<T>{}, pack<Ts...>{}, args...);
}

int main() {
    // gcc ok with both, clang rejects both as ambiguous
    foo(pack<int>{}, pack<int>{});
    foo(pack<int>{}, pack<int>{}, 0);
}

I'd expect all the calls to be OK in every version of this. What is the expected outcome of the above code examples?

2
I'd expect all the calls to be OK in every version of this. Why? (I didn't.)Walter
@Walter Because the first overload is more specialized than the second.Barry

2 Answers

4
votes

I believe now that clang is correct to reject and gcc is incorrect to accept those forms that it does. Here's a simplified example:

template <class...> struct pack { };

// (1)
template <class T>
void foo(pack<T> ) { }

// (2)
template <class T, class... Ts>
void foo(pack<T, Ts...> ) { }

int main() {
    foo(pack<int>{});
}

Both overloads are valid, and deducing (2) from (1) succeeds straightforwardly. The only issue is can we deduce (1) from (2). I'd initially think no... But [temp.deduct.type]/9 states:

If P has a form that contains <T> or <i>, then each argument Pi of the respective template argument list of P is compared with the corresponding argument Ai of the corresponding template argument list of A. [...] During partial ordering (14.8.2.4), if Ai was originally a pack expansion:
— if P does not contain a template argument corresponding to Ai then Ai is ignored;

So when we synthesize types for <T, Ts...> (say <U, Xs...>), we deduce T=U and then there is no template argument corresponding to the pack expension Xs..., so we ignore it. All the non-ignored template parameters succeeded in template deduction, so we consider deducing (1) from (2) to be success.

Since deduction succeeds in both directions, neither function template is considered more specialized than the other, and the call should be ambiguous.


I have not yet submitted a bug report, waiting on some confirmation from the community.

-1
votes

Let's first simplify the problem and consider

template <class...> struct pack {};

template <class T>
void foo(pack<T>) {}

template <class T, class... Ts>
void foo(pack<T,Ts...>) {}

int main() {
  foo(pack<int>{});
}

which clang complains about and refused to compile, claiming that there is ambiguity between void foo(pack<T>) [with T=int] and void foo(pack<T,Ts...>) [with T=int, Ts=<>]. Situations like this are resolved using partial ordering of overloaded function templates, which essentially tries to find the most specialized overload.

In the case at hand, I think the following applies:

In case of a tie, if one function template has a trailing parameter pack and the other does not, the one with the omitted parameter is considered to be more specialized than the one with the empty parameter pack.

Thus it appears that the first should be preferred and clang was wrong.