3
votes

Given overloaded functions f1:

void f1(int);
int f1(char);

And a class template X with member template f:

template<class T>
struct X
{
    template<class U>
    static void f(T(*p)(U));
};

The compiler is able to resolve f1 from part of the function type:

X<int>::f(f1); // T = int (specified), U = char (deduced)
X<void>::f(f1); // T = void (specified), U = int (deduced)

However, a variadic class template Y with parameter pack at both sides:

template<class... T>
struct Y
{
    template<class... U>
    static void f(T(*...p)(U));
};

Fail to do the same thing:

Y<int>::f(f1); // error
Y<void>::f(f1); // error
Y<int, void>::f(f1, f1); // error

Note that it's OK if the parameter pack is only at one side:

template<class... T>
struct Z1
{
    template<class U>
    static void f(T(*...p)(U));
};

template<class T>
struct Z2
{
    template<class... U>
    static void f(T(*...p)(U));
};

Z1<int>::f(f1); // ok
Z2<void>::f(f1); // ok

This showns a problem: the outer parameter pack T cannot be expanded while the inner parameter pack U is still dependant. I imagine the compiler could expand Y::f to something like below when Y<int, void> is instantiated:

template<class... U>
void f(int(*p0)(U0), void(*p1)(U1));

where U0 and U1 denote the first 2 elements of parameter pack U.

But it seems that compilers(g++/clang) refuse to do so and leave the whole p unexpended. where in the standard does it specify such a behavior? Could it be a standard defect or something to improve?

1
I think that the problem is in substituting the explicitly specified argument of the outer pack.. Since at that time, the inner pack is still empty, so you get a size mismatch during the expansion. However I dont remember the rules exactly here. - Johannes Schaub - litb
I think the rule to apply here is "All of the parameter packs expanded by a pack expansion shall have the same number of arguments specified." - Johannes Schaub - litb

1 Answers

0
votes

I've extracted another code snippet that works, although still doesn't satisfies your needs, but may lead you to something:

void f1(int)
{

}

int f1(char)
{
    return 0;
}

template <class Out, class In>
struct UniFunction
{
    typedef Out fn(In);
    typedef fn* ptr;
};

template<class... T>
struct Y
{
  //template<class... U>
  //static void f(T(*...p)(U)...);
    template <class... U>
    struct YY
    {
        static void f(typename UniFunction<T, U>::ptr...)
        {

        }
    };


};

int main( int argc, char** argv )
{

    Y<int>::YY<char>::f(f1);

    return 0;
}

This UniFunction is just for clarity and maybe show that the double parallel expansion of parameter pack isn't impossible, there's just a small problem.

I think you can still enforce argument type deduction if you provide additionally a "template function" (structure template with typedefs) that will extract (using type_traits or something) the argument type and return type from the function pointer. By having that, you should be able to provide the Y::f function with just one template parameter (kinda X...), extract the argument type from that X being a function pointer, then pass it explicitly to YY template, as shown here.