I'm interested to understand what exactly the trailing auto&& return type means, specifically as distinguished from decltype(auto), which doesn't work here, and an unspecified return type, which also doesn't work.
In the code below, fn returns the x_ field of the argument. When the argument is an lvalue, x_ comes back as an lvalue, etc.
In the examples of fn_bad[123], it seems to return int, even when an lvalue argument is provided. I can see why -> auto would cause this, but I expected -> decltype(auto) to return int&. Why does only -> auto&& work?
#include <utility>
struct Foo { int x_; };
int main() {
auto fn_bad1 = [](auto&& foo) -> decltype(auto) { return std::forward<decltype(foo)>(foo).x_; };
auto fn_bad2 = [](auto&& foo) -> auto { return std::forward<decltype(foo)>(foo).x_; };
auto fn_bad3 = [](auto&& foo) { return std::forward<decltype(foo)>(foo).x_; };
auto fn = [](auto&& foo) -> auto&& { return std::forward<decltype(foo)>(foo).x_; };
Foo a{};
fn(a) = fn(Foo{100}); // doesn't compile with bad1, bad2, bad3
}
but I expected ->
decltype(auto)to returnint&
This is expected behavior of decltype,
(emphasis mine)
Inspects the declared type of an entity or the type and value category of an expression.
1) If the argument is an unparenthesized id-expression or an unparenthesized class member access expression, then decltype yields the type of the entity named by this expression.
So the result of decltype(auto) on std::forward<decltype(foo)>(foo).x_ yields the type of the data member x_, i.e. int.
If you add parentheses as
[](auto&& foo) -> decltype(auto) { return (std::forward<decltype(foo)>(foo).x_); };
// ^ ^
Then
2) If the argument is any other expression of type
T, anda) if the value category of expression is xvalue, then
decltypeyieldsT&&;
b) if the value category of expression is lvalue, thendecltypeyieldsT&;
c) if the value category of expression is prvalue, thendecltypeyieldsT.Note that if the name of an object is parenthesized, it is treated as an ordinary lvalue expression, thus
decltype(x)anddecltype((x))are often different types.
Then, as you said, when pass an lvalue to the lambda the expression (std::forward<decltype(foo)>(foo).x_) is an lvalue, then the return type would be int&; when pass an rvalue the expression is an xvalue, then return type would be int&& (which might cause dangled reference trouble).
For the 2nd case, according to the normal rule of template argument deduction, whenever passed lvalue or rvalue the return type is always int.
The 3rd case is the same as the 2nd one.
For the 4th case, the special rule for forwarding reference is applied, then the return type would be int& when the return expression is lvalue, and int&& when the return expression is rvalue.
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