Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What's the difference between result_of<F(Args...> and decltype<f(args...)>?

Tags:

c++

c++11

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.)

like image 407
KnowItAllWannabe Avatar asked Mar 28 '13 03:03

KnowItAllWannabe


2 Answers

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.

like image 111
Luc Danton Avatar answered Sep 29 '22 10:09

Luc Danton


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.

like image 40
Nawaz Avatar answered Sep 29 '22 11:09

Nawaz