Deducing return and parameter type from std::funct

2019-07-17 03:02发布

问题:

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?

回答1:

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.