I see that std::async
is specified as follows:
template <class F, class... Args> // copied out of the standard
future<typename result_of<F(Args...)>::type>
async(F&& f, Args&&... args);
I had expected it to be declared like this:
template <class F, class... Args>
auto async(F&& f, Args&&... args) ->
future<decltype(forward<F>(f)(forward<Args>(args)...)>;
Would that be equivalent, or is there some way in which the use of result_of
is preferable to the use of decltype
? (I understand that result_of
works with types, while decltype
works with expressions.)
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
.
No difference at all from functional point of view. However, the decltype
version uses trailing-return-type, that is one difference from programming point of view.
In C++11, the std::result_of
is not absolutely necessary, one can use decltype
instead, like the way you've used. But std::result_of
is backward-compatible with third party libraries (older ones) such as Boost which has result_of
. I don't see much advantage, though.
Personally, I prefer to use decltype
as it is more powerful and works with any entities, whereas result_of
works with callable entities only. So if I use decltype
, I can use it everywhere. But with result_of
, I've to occasionally switch to decltype
(that is, when the entity is not callable). See this at github where I've used decltype
in return type of all functions.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With