14
votes

This is similar to the question, but a more specific case. This time, no compiler work as expected.

template<class T>
struct nondeduced
{
    using type = T;
};

template<class T>
using nondeduced_t = typename nondeduced<T>::type;

template<class... T, class U>
void f(void(*)(nondeduced_t<T>..., U)) {}

void g(int, char) { }

int main()
{
    f<int>(g); // error?
}

In the above example, the parameter pack T cannot be deduced, but the compiler should be able to deduce U after explicit arguments substitution for pack T (i.e. single int in this case).

The above is expected to work without the nondeduced_t trick as well:

template<class... T, class U>
void f(void(*)(T..., U)) {}

Because the parameter pack T is already in non-deduced context according to [temp.deduct.type]p5

The non-deduced contexts are:

  • A function parameter pack that does not occur at the end of the parameter-declaration-list.

Unfortunately, no compiler I tested (g++/clang) accept the code. Notably something like below works on both g++ & clang.

template<class... T>
void f(void(*)(nondeduced_t<T>..., char)) {}

And again, this doesn't work on both:

template<class... T>
void f(void(*)(T..., char)) {}

Is my expectation wrong?

1
Someone please help me learn a little here. I would have thought that template<class... T, class U> void f(void(*)(nondeduced_t<T>..., U)) {} was ill-formed because a variadic parameter pack is appearing first.AndyG
It appears that this is not clearly specified by the speccccJohannes Schaub - litb
I believe T... should be greedy under the standard -- it should leave nothing for U to consume, so there is no ambiguity. Right?Yakk - Adam Nevraumont
Is this behaviour normal?BiagioF

1 Answers

1
votes

By [temp.deduct.type]p5 one of the non-deduced-context is

A function parameter pack that does not occur at the end of the parameter-declaration-list.

Parameter packs that doesn't appear as the last argument of a template functions are never deduced, but is completely right to specify the parameter types disabling the deduction. e.g

template<class T1, class ... Types> void g1(Types ..., T1);

g1<int, int, int>(1,2,3);  // works by non-deduction
g1(1,2,3)                  // violate the rule above by non-deduced context

But changing the order of function argument even leaving the template parameters as they are, remove the non-deduced context condition and break the infinite expansion of parameter pack. e.g

template<class T1, class ... Types> void g1(T1, Types ...);
g1(1,2,3)                 // works because its a deduced context.

There're two reasons your code don't compile:

  1. The order of function argument create a non-deduced-context which cause the type of the parameter pack T in the pattern stated in function f would never be deduced.

  2. The template parameter T appears only as a qualifiers in function arguments(e.g nondeduced_t) and not directly specified as a function argument(which allow argument deduction).

To make the code compile you have either place the expansion of the parameter pack as it is forgetting the nondeduced_t indirect, as

template<class... T,class U>
void f( void(*)(U,T...) ) { }

f(g);

or changing the order of template parameters and specify the template argument on function call, as

template<class U,class... T>
void f( void(*)(U,typename nondeduced<T>::type...) ) {}

f<int,char>(g);