Search code examples
c++templatesc++11template-argument-deduction

Why doesn't my template parameter pack work?


I have been trying to get a templated function to call a set of similar functions to avoid boilerplate.

FooA(float a, Widget* w);
FooB(int b, Widget* w);
FooC(int c, Widget* w);
FooD(int d, int e, Widget* w);

template <typename... Args>
static void RunFoo(void (*foo)(Args..., Widget*), Args... args) {
    Widget w
    foo(args, &w);
}

I don't understand why this works fine with:

float a = 10;
RunFoo(FooA, a);

But it fails whenever I try with multiple arguments:

int a = 10;
int b = 3;
RunFoo(FooD, a, b);

It fails to compile with the error: "candidate template ignored: failed template argument deduction"

Is this beyond the capabilities of c++ templates?


Solution

  • template<class T>struct tag {using type=T;};
    template<class Tag>using type_t=typename Tag::type;
    template<class T>using block_deduction=type_t<tag<T>>;
    
    template <typename... Args>
    static void RunFoo(block_deduction<void(*)(Args...,Widget*)> foo, Args... args) {
      Widget w
      foo(args, &w);
    }
    

    you cannot deduce like Args..., Widget* -- parameter packs must be last in general.

    Both cases are "equal" in deduction. block_deduction prevents deduction from occurring on that parameter. So the other deduction happens, and works.

    Note that such deduction is usually a bad idea. You don't want to deduce one parameter, and generate a function pointer elsewhere. It is brittle.

    This might be better:

    template <class F, class... Args>
    static std::result_of_t<F(Args..., Widget*)> RunFoo(F&& f, Args&&... args) {
      Widget w
      return std::forward<F>(f)(std::forward<Args>(args)..., &w);
    }
    

    if you are passing an overload set in, wrap the overload set up in an overload set object. std::result_of_t<?> is C++14, replace with typename std::result_of<?>::type in C++11.