Search code examples
c++templatesstd-function

Deducing return and parameter type from std::function passed as a template function argument?


I've been looking around SO for a while now but cannot find quite the answer that I am looking for - this question is probably the closest to what I am thinking.

In a sentence: is it possible to declare a template function that takes a parameter of an std::function and deduce template parameters for both the return type and the parameter types of the function? Example:

//this works to pass the std::function in
template<class T>
void doSomething(std::function<T> f) {
    f();
}

//this is more what i am looking for - can R and P be deduced automatically - does not work!
template<class R, class P>
void doSomethingElse(std::function<R(P)> f) {
    f();
}

Is this because the function signature or function type is considered one thing in itself, and as such cannot be "broken" up? I realise there are decltype and std::result_of but cannot think how I might use them here.

As an additional point, how might I extend the second example to have multiple parameters and deduction, using variadic templates?


Solution

  • template<class R, class P>
    void doSomethingElse(std::function<R(P)> f) {
        f(P{});
    }
    

    Will work, but it only works if you pass a std::function to the function and that function has one non void parameter. This is kind of limiting though. You can use

    template<class R, class... Args, class... Ts>
    void doSomethingElse(std::function<R(Args...)> f, Ts&&... args) {
        f(std::forward<Args>(args)...);
    }
    

    Which will take any std::function and the arguments for it and calls them as if you did it in the call site. This is still limiting though because the call site requires you use a std::function so you can't pass it anything implicitly convertible to a std::function.

    With C++17 and class template argument deduction (CTAD) this is no longer an issue though. We can create an overload that takes any type, and then construct a std::function using CTAD to fill in the types for us. That would look like

    template<class Func, class... Args>
    void doSomethingElse(Func&& f, Args&&... args) {
        doSomethingElse(std::function{std::forward<Func>(f)}, std::forward<Args>(args)...);
    }
    
    template<class R, class... Args, class... Ts>
    void doSomethingElse(std::function<R(Args...)> f, Ts&&... args) {
        f(std::forward<Args>(args)...);
    }
    

    And now anything that isn't a std::function will go to void doSomethingElse(Func&& f, Args&&... args), get converted to a std::function, and get passed to void doSomethingElse(std::function<R(Args...)> f, Args&&... args) so you can use the return type and argument(s) type(s) in there.