Result_of 和 decltype 之间的区别

我在理解 C + + 0x 中对 std::result_of的需求时遇到了一些麻烦。如果我理解正确的话,result_of用于获得调用具有特定参数类型的函数对象的结果类型。例如:

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

我看不出以下代码有什么不同:

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

或者

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

我认为这两种解决方案的唯一问题是,我们需要:

  • 有一个函数的实例来在传递给 dectype 的表达式中使用它。
  • 知道一个已定义的函数构造函数。

我认为 decltyperesult_of之间的唯一区别是第一个需要一个表达式,而第二个不需要,这种想法对吗?

23481 次浏览

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;
};

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.