In a context where the type of the result of a function call must be deduced, C++ seems to be more that happy to help us, providing (at least to my knowledge the following) two solutions :
The result of type trait :
std::result_of<F(Args...)>::type
A core language syntax :
decltype(std::declval<F>()(std::declval<Args>()...);
My question is, are the any differences between the two? Is there a context where one cannot be substituted by the other and if not why did we need a type trait to do something the language could do out of the box ?
There are three differences.
Initially, std::result_of
was not required to be SFINAE-friendly. So if were to use it in a context to verify that F
was callable with Args...
, it would give you a hard error whereas decltype
with std::declval
would simply cause the likely intended substitution failure. N3462 fixed the specification to make it SFINAE-friendly.
On a compliant C++14 compiler though, both are SFINAE friendly. std::result_of_t<Fn(ArgsTypes...)>
is actually defined in terms of the latter. From [meta.trans.other]:
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 typedecltype(INVOKE (declval<Fn>(), declval<ArgTypes>()...));
otherwise, there shall be no member type.
As made clear in the language of the definition of result_of
, the type Fn
can be anything INVOKE
-able, whereas explicitly calling std::declval<F>()
only works on functions and function objects. So if you had:
using F = decltype(&X::bar);
using T1 = std::result_of_t<F(X*)>; // ok
using T2 = decltype(std::declval<F>()(std::declval<X*>()); // not ok
For some types, std::result_of
doesn't actually work due to it being illegal to specify certain type-ids (see my related question). Those types are using functions for F
(as opposed to pointers/references to function) or using abstract classes for any of the Args...
. So consider something seemingly innocuous like:
template <class F, class R = std::result_of_t<F()>>
R call(F& f) { return f(); }
int foo();
call(foo); // error, unresolved overload etc.
This fails SFINAE (at least it's not a hard error) because we'd have to form the type int()()
- a nullary function returning a function. Now, technically, we wrote our code incorrectly. It should've been:
template <class F, class R = std::result_of_t<F&()>>
R call_fixed(F& f) { return f(); }
This works. But had we made the same mistake with declval, we'd've been fine:
template <class F, class R = decltype(std::declval<F>()())>
R call_declval(F& f) { return f(); }
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