Your version doesn't work with e.g. pointers to members. A closer, but still not exact version would be:
template <class F, class... Args>
auto async(F&& f, Args&&... args)
-> future<decltype( ref(f)(forward<Args>(args)...) )>;
The only difference remaining with std::result_of
is that this forwards the functor as an lvalue (a problem your version also shares). In other words, the result of such a call (via an std::reference_wrapper<F>
) is typename std::result_of<F&(Args...)>::type
.
This is an awkward situation where several components of the Standard library (to name a few, in addition to those we've just witnessed: std::thread
, std::bind
, std::function
) are specified in terms of an elusive INVOKE(f, a0, a1, ..., aN) pseudo-expression, which isn't exactly equivalent to f(a0, a1, ... aN)
. Since std::result_of
is one of those components, and serves in fact to compute the result type of INVOKE, that's the discrepancy you're noticing.
Because there is no std::invoke
that comes in tandem with the std::result_of
type trait I am of the opinion that the latter is only useful for describing e.g. the return types of the relevant Standard Library components, when your code calls them. If you want a concise and self-documenting way of writing e.g. a return type (a very worthy goal for readability, compared to sprinkling decltype
everywhere), then I recommend you write your own alias:
template<typename F, typename... A>
using ResultOf = decltype( std::declval<F>()(std::declval<A>()...) );
(If you want the alias to be used as ResultOf<F(A...)>
instead of ResultOf<F, A...>
then you need a little bit of machinery to pattern match over the function signature.)
An added benefit of this alias is that it is SFINAE friendly, unlike std::result_of
. Yes, that is one more of its flaws. (To be fair though this has been amended for the upcoming Standard and implementations are following suit already.)
You would not be missing anything if you were using such a trait because you can adapt pointers to members thanks to std::mem_fn
.