0

I have a function printint which take an integer as parameter. How I can pass it into a variadic template as below? I have try run_callback(printint,1) but it says "no instance match with the function". Please help, thank you!

void printint(int i)
{

}

template <typename ...Args>
void run_callback(const std::function<void(Args...)>& func, Args ...as)
{
....
}

1 Answers1

4

When you call your function template, e.g., like so

run_callback(printint, 1);

the compiler will try to deduce template arguments which, when substituted into the function parameter types, will make the function parameter types match the types of the arguments. The problem here is that there are no Args... that you could put into const std::function<void(Args...)>& to make that type match the type of printint, which is void(int). Therefore, template argument deduction fails.

If you just want your run_callback to use any function with a signature that matches Args..., have it take a reference to a function with such signature:

template <typename... Args>
void run_callback(void (&func)(Args...), Args... as)
{
}

live example

This, however, will be somewhat brittle as it basically requires that the types in Args are exact matches for the parameter types of the callback. Most likely, you'd want run_callback to work with any function (or, more general, any callable) that could be called with the given args. One way to achieve this would be to have your function template accept any type as a callback but only enable the overload when the func parameter is actually a callable that is invocable using the respective argument list:

template <typename F, typename... Args>
auto run_callback(F&& f, Args&&... as) -> std::enable_if_t<std::is_invocable_v<F, Args...>>
{
    f(std::forward<Args>(as)...);  // call the callback
}

live example

Finally, if, for some reason, you really absolutely need your func parameter the be an std::function, you could hide the Args... parameter pack in func in a non-deduced context:

template <typename... Args>
void run_callback(const std::common_type_t<std::function<void(Args...)>>& func, Args... as)
{
}

live example

The trick here is the use of std::common_type_t<T>, which really is just a shorthand for std::common_type<T>::type, which will just come out to be T again in the end (Note: there's nothing special about std::common_type except that it's already there for us to use; we could use any other helper template; all we need is something that forwards its argument to a dependent name). It's impossible in general to unambiguously deduce which T you'd have to plug into C<T>::xyz to make C<T>::xyz become a certain type. It's not even guaranteed that there is such an xyz for every single T or that C<T>::xyz would be a type to begin with. For this reason, the nested-name-specifier in a qualified-id is defined to be a non-deduced context [temp.deduct.type]/5.1. Long story short, that means that Args... now appears in the type of the parameter func in such a way that the compiler will not try to deduce what Args... should be from an argument passed for the func parameter (i.e., it will not try to do the thing that caused your original code to fail). It still can deduce Args... from the as function parameter pack, however. Thus, type deduction will just make Args... be whatever the types of the arguments passed for as... are and succeed. After type deduction succeeded, the deduced Args... are substituted back into the remaining parameters and the type of func will come out to be const std::function<void(Args...)>& but now with the types of Args... taken from what they were deduced to be from the as parameter pack.

Another way to do basically the same thing would be to, e.g., wrap the argument for the first parameter in an initializer list:

run_callback({ printint }, 1);

live example

An argument that is an initializer list also makes the parameter a non-deduced context in this case [temp.deduct.type]/5.6, so the explanation for why this works is basically the same as in the previous example. Note that, while the previous approach solves the problem from within the template declaration, this approach solves it at the site of the function call.

Michael Kenzel
  • 15,508
  • 2
  • 30
  • 39