14

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

KnowItAllWannabe
  • 12,972
  • 8
  • 50
  • 91
  • Is it _specified_ or _implemented_ with `result_of`? Because that could just be a whim of the guy who implemented it, or it could have been implemented before `decltype` made it to the target compiler. In Apple's libc++, it uses neither. – zneak Mar 28 '13 at 04:03
  • @zneak: The declaration I showed is copied out of the standard, so `std::async` is specified via `result_of`. Implementations can do whatever they like, as long as the behavior they provide is the same as what's specified. – KnowItAllWannabe Mar 28 '13 at 06:03
  • Just know that there are some [unfortunate implications with the `std::result_of` syntax](http://stackoverflow.com/a/15489789/500104). – Xeo Mar 28 '13 at 07:44

3 Answers3

11

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.

Luc Danton
  • 34,649
  • 6
  • 70
  • 114
  • I modified the original code so that the `decltype` version applies `std::forward` to `f`, thus presumably eliminating the problem that all function objects were treated at lvalues. Thanks for pointing this out. – KnowItAllWannabe Mar 30 '13 at 04:48
  • I've accepted this as the answer, because I think the observation that the `decltype` version wouldn't support pointers to members is important. – KnowItAllWannabe Apr 01 '13 at 17:32
5

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.

Nawaz
  • 353,942
  • 115
  • 666
  • 851
  • Another programming difference is that the `decltype` version requires the use of `forward`, whereas the `return_of` version apparently does not. – KnowItAllWannabe Mar 28 '13 at 03:39
  • @KnowItAllWannabe: As you've noticed yourself (in the question), `std::result_of` works with *types*, so there is nothing to forward. – Nawaz Mar 28 '13 at 03:40
  • I think you mean `std::result_of` where you wrote `std::result_type`. – KnowItAllWannabe Mar 28 '13 at 03:48
  • @KnowItAllWannabe: Yes. corrected long back. Refresh your page. And see my edited answer as well. – Nawaz Mar 28 '13 at 03:50
  • I'm referring to the `std::result_type` in the second paragraph. But now I'm thinking maybe you want it there, because you're talking about backwards compatibility. – KnowItAllWannabe Mar 28 '13 at 03:55
  • @zneak: it is `std::result_of`, not `std::result_type`. please don't edit the answer again! – Nawaz Mar 28 '13 at 04:10
  • @Nawaz, it was `result_type` at first, and I changed it to `result_of`. Seeing @KnowItAllWannabe's comment, I reverted it because I didn't want to have to check or be proven wrong. (You can see it in your post's history, rev 5-6.) – zneak Mar 28 '13 at 04:12
  • @zneak: Oh. I saw that now. Anyway, it should be `std::result_of`. `result_type` was a typo. – Nawaz Mar 28 '13 at 04:14
5

You already had the difference in your questions: "I understand that result_of works with types, while decltype works with expressions."

They both provide the same functionality (std::result_of is even implemented in terms of decltype nowadays) and the difference for your example is mostly non-existent, because you already have the necessary variables to build an expression.

That said, the difference boils down to syntactic sugar for when you only have the types. Consider:

typename std::result_of< F( A, B, C ) >::type

vs.

decltype( std::declval<F>()( std::declval<A>(), std::declval<B>(), std::declval<C>() )

And just remember that std::result_of is an option in those cases.

Note that there are also cases where decltype can be used in a way that std::result_of can't. Consider decltype( a + b ), you can not find an F to create an equivalent std::result_of< F( A, B ) > as far as I know.

Daniel Frey
  • 55,810
  • 13
  • 122
  • 180