Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

auto&& return type from a C++ lambda

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
}
like image 336
E_G Avatar asked Sep 21 '26 23:09

E_G


1 Answers

but I expected -> decltype(auto) to return int&

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, and

a) if the value category of expression is xvalue, then decltype yields T&&;
b) if the value category of expression is lvalue, then decltype yields T&;
c) if the value category of expression is prvalue, then decltype yields T.

Note that if the name of an object is parenthesized, it is treated as an ordinary lvalue expression, thus decltype(x) and decltype((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.

like image 156
songyuanyao Avatar answered Sep 25 '26 02:09

songyuanyao



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!