1

Why does auto&& var2 not mean rvalue reference in the code below?

I would be grateful to have some help with this question.

Widget&& var1 = someWidget;      // here, “&&” means rvalue reference
auto&& var2 = var1;              // here, “&&” does not mean rvalue reference

Given that Widget&& var1 is an rvalue reference, why isn't it an rvalue?

Why does auto&& var2 = var1; not mean rvalue reference?

JFMR
  • 23,265
  • 4
  • 52
  • 76
sunshilong369
  • 646
  • 1
  • 5
  • 17
  • `auto&& var2 = var1;` means an lvalue reference to an lvalue. `auto&& var2 = std::move(var1);` means an lvalue reference to an rvalue. `var2` is itself an lvalue. `std::move(var2)` is a cast to treat the lvalue as an rvalue. – Eljay May 31 '20 at 15:52
  • Could you explain it more in detial?Why `var1` is a lvalue? – sunshilong369 May 31 '20 at 16:04
  • `var1` is an lvalue because it _named_. Things that are _named_ are lvalues. – Eljay May 31 '20 at 16:14
  • @Eljay & 眠りネロク I agree with you that a lvalue is the one which is named.While i think `Widget&& var1` indcates `var1` is a right reference to `someWidget`.I think they are both right.So i am confused. – sunshilong369 May 31 '20 at 16:20
  • `var1` is an lvalue. `someWidget` is an lvalue. `var2` is an lvalue. Things with _names_ are lvalues. – Eljay May 31 '20 at 16:23
  • Thank you for the clarification.I agree with your last comment.Then why coment for the code says "&& means rvalue reference"(for more details, see https://isocpp.org/blog/2012/11/universal-references-in-c11-scott-meyers)? – sunshilong369 May 31 '20 at 16:34
  • @sunshilong369 **rvalue** and **rvalue reference** are different things. The former is a kind of *value category*. The latter is a kind of *reference* that can only be initialized with an *rvalue* object (an object whose *value category* is *rvalue*). – JFMR May 31 '20 at 16:42
  • As an example with your code: `var1` declared as `Widget&& var1` is a *reference* to `Widget`, specifically an **rvalue reference** to `Widget` – This is the **type** of `var1`. However, when using `var1` in an expression (without marking it with `std::move`), then `var1` is an **lvalue** because it has a name – This the **value category** of `var1`. – JFMR May 31 '20 at 16:51
  • I think i understand your last comment in some degree.But i know i don't completly understand it. I agree with that I am mixing up the type of a reference and its value category indeed.Could you suggest some documentation for me to study?I want to dig deep.I would go out my way to understand it(to separate them).Thank you for your help . – sunshilong369 May 31 '20 at 17:05
  • Maybe this explanation will help: https://stackoverflow.com/questions/3601602/what-are-rvalues-lvalues-xvalues-glvalues-and-prvalues – Eljay May 31 '20 at 17:23
  • @眠りネロク If i understand your last reply(the edited answer) correctly, since the `var2` is the type of `auto&&`, the real type of `var2` should be deduced by the right part of the expression(with your help, i could understand it now. Thank you.).Meanwhile, "Widget&&" is **always** the type of rvalue reference.I wonder that whether `auto&&` is a term or not, though i understand that the keyword `auto` indicates that the type should be duduced. – sunshilong369 Jun 01 '20 at 05:44
  • 1
    @sunshilong369 Well, the term for `auto&&` is **universal reference** or **forwarding reference**. A *universal reference* will become an *lvalue reference* or an *rvalue reference* depending on whether it is initialized with an *lvalue* or *rvalue*, respectively. Note that the difference with an ordinary reference is that type deduction occurs (i.e., the `auto`) and a *universal reference* always results in a reference. – JFMR Jun 01 '20 at 06:37

1 Answers1

1

Given that Widget&& var1 is an rvalue reference, why isn't it an rvalue?

someWidget, var1, and var2 all have a name and, therefore, they are all lvalues regardless of their declared type.

The term rvalue reference is about the type of the reference, var1, whereas the term rvalue is about the value category of the expression var1:

  • var1 declared as Widget&& var1 is a reference to Widget, specifically an rvalue reference to Widget (i.e., Widget&&) – This is the type of var1, which is a reference. The reference type tells you how the reference can be initialized: an rvalue reference can only be initialized with an rvalue, whereas a non-const lvalue reference can only be initialized with an lvalue.

  • When using var1 in an expression (without marking it with std::move), then var1 is an lvalue because it has a name – This the value category of var1, which is a property of the expression var1 orthogonal to its type.

Note also that the following statement doesn't compile:

Widget&& var1 = someWidget;

This compilation error is because var1 is an rvalue reference and, therefore, can only be initialized with an rvalue. However, someWidget is an lvalue since it has a name, and it isn't marked with std::move for moving. For the statement to compile you could have done one of the following:

  • Declaring v1 as an lvalue reference instead:

    Widget& var1 = someWidget;
    

    The lvalue reference var1 can be initialized with someWidget, an lvalue.

  • Marking someWidget for moving with std::move():

    Widget&& var1 = std::move(someWidget);
    

    The rvalue reference var1 can be initialized with std::move(someWidget), an rvalue.


Why does auto&& var2 not mean rvalue reference?

var2 is a universal reference because there is type deduction involved. As such, var2 will become either an lvalue reference or rvalue reference depending on how it is initialized (var2 will always be a reference).

The auto in auto&& var2 = var1; deduces to Widget& because var1 is an lvalue (i.e., var1 is a named object and you haven't applied std::move() to it). Then, Widget& && results in Widget& due to reference collapsing. To sum up, the statement:

auto&& var2 = var1;

after type deduction becomes:

Widget& var2 = var1;

So var2 is actually an lvalue reference.


If you want var2 to be an rvalue reference, you can apply std::move() to the initializing object:

auto&& var2 = std::move(var1);

after type deduction, this results in:

Widget&& var2 = var1;

var2 is an rvalue reference in this case.

JFMR
  • 23,265
  • 4
  • 52
  • 76
  • Thanks a lot. Yes, `var1` has a name indeed. `someWidget` has a name too, `someWidget` is a name for an object.So `someWidget` is a lvalue. Since there is a `&&` in `Widget&& var1`, i think `var1` is a rvalue reference to `someWidget`.Where am i wrong? – sunshilong369 May 31 '20 at 15:58
  • @sunshilong369 There is no contradiction because *type* and *value category* are different properties: `someWidget` is an *lvalue*. Its type, however, is `Widget&&`, i.e., an *rvalue reference*. – JFMR May 31 '20 at 16:01
  • I think you are mixing up the *type* of a reference and its *value category*. `someWidget`, `var1`, and `var2` all have a name and, therefore, they are all *lvalues* (their value category). The *value category* tells you what you can do with these objects. For example, you can't directly use this objects to initialize an *rvalue reference*, whereas you can use them to initialize an *lvalue reference*. Whether a reference is of **type** *lvalue* or *rvalue*, tells you how it can be initialized. For example, you can initialize an rvalue reference with a temporary, but not an lvalue reference. – JFMR May 31 '20 at 16:36
  • @sunshilong369 I've edited your question to include the original one as well as your follow-up question. I've also extended my answer accordingly. I hope it helps. – JFMR May 31 '20 at 22:26
  • 1
    I think i mostly understand it.Thank you for your generous help. – sunshilong369 Jun 01 '20 at 06:01