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!

1

There are 1 best solutions below

0
Artyer On

There are exactly two cases where decltype(auto) is different from auto&&.

As you've noted, when you return a prvalue, decltype(auto) returns by value, and auto&& returns a dangling rvalue reference (probably undesireable).

The second case is when you return an entity directly. E.g.:

struct X {
    int i;
    decltype(auto) return_entity() { return i; }
    decltype(auto) return_expression() { return (i); }
};

static_assert(std::is_same_v<decltype(X{}.return_entity()), int>);
static_assert(std::is_same_v<decltype(X{}.return_expression()), int&>);

(Because decltype(i) is int, but decltype((i)) is int&).


In every other scenario they are the same. You may want to use auto&& to emphasise you are returning a reference, or decltype(auto) to emphasise you are perfect forwarding something.

As an example, this documentation for std::forward_like is shown as returning auto&& because it always returns a reference.

But if you could possibly return a prvalue, decltype(auto) is obviously preferable.