112

I have some trouble understanding the need for std::result_of in C++0x. If I understood correctly, result_of is used to obtain the resulting type of invoking a function object with certain types of parameters. For example:

template <typename F, typename Arg>
typename std::result_of<F(Arg)>::type
invoke(F f, Arg a)
{
    return f(a);
}

I don't really see the difference with the following code:

template <typename F, typename Arg>
auto invoke(F f, Arg a) -> decltype(f(a)) //uses the f parameter
{
    return f(a);
}

or

template <typename F, typename Arg>
auto invoke(F f, Arg a) -> decltype(F()(a)); //"constructs" an F
{
    return f(a);
}

The only problem I can see with these two solutions is that we need to either:

  • have an instance of the functor to use it in the expression passed to decltype.
  • know a defined constructor for the functor.

Am I right in thinking that the only difference between decltype and result_of is that the first one needs an expression whereas the second does not?

Luc Touraille
  • 79,925
  • 15
  • 92
  • 137

2 Answers2

94

result_of was introduced in Boost, and then included in TR1, and finally in C++0x. Therefore result_of has an advantage that is backward-compatible (with a suitable library).

decltype is an entirely new thing in C++0x, does not restrict only to return type of a function, and is a language feature.


Anyway, on gcc 4.5, result_of is implemented in terms of decltype:

  template<typename _Signature>
    class result_of;

  template<typename _Functor, typename... _ArgTypes>
    struct result_of<_Functor(_ArgTypes...)>
    {
      typedef
        decltype( std::declval<_Functor>()(std::declval<_ArgTypes>()...) )
        type;
    };
kennytm
  • 510,854
  • 105
  • 1,084
  • 1,005
  • Ok, so it is mainly a convenience class to avoid the ugly expression in decltype, right? – Luc Touraille Apr 22 '10 at 12:09
  • 4
    As far as I understand `decltype` is uglier but also more powerful. `result_of` can only be used for types that are callable and it requires types as arguments. For example, you cannot use `result_of` here: `template auto sum( T t, U u ) -> decltype( t + u );` if the arguments can be arithmetic types (there is no function `F` such that you can define `F(T,U)` to represent `t+u`. For user defined types you could. In the same way (I have not really played with it) I imagine that calls to member methods might be hard to do with `result_of` without using binders or lambdas – David Rodríguez - dribeas Dec 22 '11 at 09:19
  • 2
    One note, decltype needs arguments to call the function with AFAIK, so without result_of<> it's awkward to get the type returned by a template without relying on the arguments having valid default constructors. – Robert Mason Nov 01 '12 at 16:46
  • 3
    @RobertMason: Those arguments can be retrieved using `std::declval`, like the code I've shown above. Of course, this is ugly :) – kennytm Nov 01 '12 at 17:05
  • 1
    @DavidRodríguez-dribeas Your comment has unclosed parentheses that open with "(there is" :( – Navin Apr 22 '15 at 02:30
  • result_of won't work with functions that take by non-const reference AFAIK – Mark K Cowan Apr 06 '16 at 21:48
  • 2
    Another note: `result_of` and its helper type `result_of_t` are deprecated as of C++17 in favour of `invoke_result` and `invoke_result_t`, alleviating some restrictions of the former. These are listed at the bottom of https://en.cppreference.com/w/cpp/types/result_of. – sigma Jun 02 '19 at 10:18
12

If you need the type of something that isn't something like a function call, std::result_of just doesn't apply. decltype() can give you the type of any expression.

If we restrict ourselves to just the different ways of determining the return type of a function call (between std::result_of_t<F(Args...)> and decltype(std::declval<F>()(std::declval<Args>()...)), then there is a difference.

std::result_of<F(Args...) is defined as:

If the expression INVOKE (declval<Fn>(), declval<ArgTypes>()...) is well formed when treated as an unevaluated operand (Clause 5), the member typedef type shall name the type decltype(INVOKE (declval<Fn>(), declval<ArgTypes>()...)); otherwise, there shall be no member type.

The difference between result_of<F(Args..)>::type and decltype(std::declval<F>()(std::declval<Args>()...) is all about that INVOKE. Using declval/decltype directly, in addition to being quite a bit longer to type, is only valid if F is directly callable (a function object type or a function or a function pointer). result_of additionally supports pointers to members functions and pointers to member data.

Initially, using declval/decltype guaranteed a SFINAE-friendly expression, whereas std::result_of could give you a hard error instead of a deduction failure. That has been corrected in C++14: std::result_of is now required to be SFINAE-friendly (thanks to this paper).

So on a conforming C++14 compiler, std::result_of_t<F(Args...)> is strictly superior. It's clearer, shorter, and correctly supports more Fs.


Unless, that is, you're using it in a context where you don't want to allow pointers to members, so std::result_of_t would succeed in a case where you might want it to fail.

With exceptions. While it supports pointers to members, result_of will not work if you try to instantiate an invalid type-id. These would include a function returning a function or taking abstract types by value. Ex.:

template <class F, class R = result_of_t<F()>>
R call(F& f) { return f(); }

int answer() { return 42; }

call(answer); // nope

The correct usage would've been result_of_t<F&()>, but that's a detail you don't have to remember with decltype.

Barry
  • 286,269
  • 29
  • 621
  • 977
  • For a non-reference type `T` and a function `template result_of_t call(F&& f, T&& arg) { return std::forward(f)(std::move(arg)); }`, is the usage of `result_of_t` correct? – Zizheng Tai Jun 20 '16 at 10:13
  • Also, if the argument we pass to `f` is a `const T`, should we use `result_of_t`? – Zizheng Tai Jun 20 '16 at 10:17