Take three functions returning prvalue, lvalue, xvalue:
int f();
int& f(int);
int&& f(int, int);
and call them via a function returning decltype(auto)
decltype(auto) returnsDecltypeOf(auto... x) {
return f(x...);
}
This seems to work the way one would expect:
static_assert(std::is_same_v<decltype(returnsDecltypeOf()), int>);
static_assert(std::is_same_v<decltype(returnsDecltypeOf(1)), int&>);
static_assert(std::is_same_v<decltype(returnsDecltypeOf(1,1)), int&&>);
but if I change decltype(auto) to auto&&,
auto&& returnsAutoRefRef(auto... x) {
return f(x...);
}
things change:
static_assert(std::is_same_v<decltype(returnsAutoRefRef()), int&&>);
static_assert(std::is_same_v<decltype(returnsAutoRefRef(1)), int&>);
static_assert(std::is_same_v<decltype(returnsAutoRefRef(1,1)), int&&>);
I see the obvious difference that returnsAutoRefRef always returns a reference, so it can do the wrong thing, e.g. in the case of f(), where it will return a prvalue expression through an auto&& return value, which will be a dangling int&&.
Therefore it looks like decltype(auto) (or, if we plan to leverage SFINAE, even better -> decltype(return-expr)) is indeed the way to go to perfect forward the return expression back to the caller.
So my question is, when would I want to return auto&&? Why couldn't I just do decltype(auto)?
As far as I understand C++, a leading return type of decltype(auto) is the same thing as leading auto + trailing -> decltype(return expression), besides the fact that the latter is SFINAE-friendly and the former is not. Now, I'm not sure how much this SFINAE-specific thing influences the answer to the question!
There are exactly two cases where
decltype(auto)is different fromauto&&.As you've noted, when you return a prvalue,
decltype(auto)returns by value, andauto&&returns a dangling rvalue reference (probably undesireable).The second case is when you return an entity directly. E.g.:
(Because
decltype(i)isint, butdecltype((i))isint&).In every other scenario they are the same. You may want to use
auto&&to emphasise you are returning a reference, ordecltype(auto)to emphasise you are perfect forwarding something.As an example, this documentation for
std::forward_likeis shown as returningauto&&because it always returns a reference.But if you could possibly return a prvalue,
decltype(auto)is obviously preferable.