问题
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 ?
回答1:
result_of
yields the return type of std::invoke when applied to the given types - it's even used in invoke
's declared return type. However, invoke
covers far more scenarios than just ordinary non-member function calls. The expression in decltype(…)
you showed is just one of many that result_of
examines.
Thus your options are only equivalent if F
is guaranteed to be a function object type. Otherwise result_of<…>
may be valid in cases in which decltype(…)
is not:
struct A {int i;};
decltype(std::declval<int A::*>()(std::declval<A>())) // error
std::result_of_t<int A::*(A)> // = int&&, treated as a member access
Demo. (Note that result_of
is SFINAE friendly as of C++14, i.e. invalid types cause a smooth deduction failure as for decltype
.)
回答2:
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 thatF
was callable withArgs...
, it would give you a hard error whereasdecltype
withstd::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 typeFn
can be anythingINVOKE
-able, whereas explicitly callingstd::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 forF
(as opposed to pointers/references to function) or using abstract classes for any of theArgs...
. 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(); }
回答3:
Take some C++ programmers who aren't very experienced and ask them to guess what each of the code snippets does.
How many get the first right? How many get the second right?
Giving this operation a name makes it much more readable.
Now take some slightly more experienced C++ programmers and ask them to get the result type of a function call with and without using result_of. How many get them right and how long does it take them?
This is already non-trivial metaprogramming, and having it encapsulated makes it easier to discover with Google and easier to get right.
Also, the first version is quite a bit shorter.
So result_of is very useful, and since the standard library itself needs it quite a few times, making it available to the programmer too is trivial.
来源:https://stackoverflow.com/questions/35834026/on-the-various-ways-of-getting-the-result-type-of-a-function