42

I posted this answer: https://stackoverflow.com/a/28459180/2642059 Which contains the following code:

void foo(string&& bar){
    string* temp = &bar;

    cout << *temp << " @:" << temp << endl;
}

Is bar an rvalue or an lvalue?

I ask because I obviously cannot take the address of an rvalue, yet I can take the address of an rvalue reference as is done here.

If you can perform any operation on an rvalue reference that you can on an lvalue reference what is the point in differentiating between the two with the "&&" instead of just an "&"?

Community
  • 1
  • 1
Jonathan Mee
  • 37,899
  • 23
  • 129
  • 288
  • 2
    The entire point of rvalues is to allow move semantics, they are in most ways (except their type) just references. – Guvante Feb 12 '15 at 17:01
  • @Guvante [MikeSeymour](http://stackoverflow.com/users/204847/mike-seymour)'s answer helped me understand that. But as with the comment I posted to him, won't that cause trouble if we use it to move construct twice? Say: `string temp1(bar), temp2(bar);` – Jonathan Mee Feb 12 '15 at 17:07
  • 2
    In your example you aren't moving twice, you are copying twice, as `bar` is an lvalue (its type is an rvalue, weird I know). If you called `std::move` twice you could run into issues however. – Guvante Feb 12 '15 at 17:20
  • 3
    @JonathanMee And this double use of `bar` is *precisely* why `bar` is an lvalue - so that such double use doesn't give you issues. It only gets treated as an rvalue when you do it explicitly: `std::move(bar)`. – Angew is no longer proud of SO Feb 12 '15 at 17:23
  • @Guvante Wait a sec? An rvalue reference is an lvalue, so it won't automatically use the `move` constructor? I just don't get it. Why do we call it an rvalue reference at all then? The only thing I thought it did was require move construction. But if doesn't even do that... – Jonathan Mee Feb 12 '15 at 17:25
  • 1
    @JonathanMee: You are thinking on the wrong side of the fence. An rvalue reference is moved **into** not moved **out of**. You know that when your function is called `bar` will be destroyed, that is what you get. However if you then call a function within yours you don't make that guarantee unless you explicitly say it. – Guvante Feb 12 '15 at 17:27
  • 5
    "Why do we call it an rvalue reference at all then?" - because it's a reference to an rvalue (or pedantically, to an object denoted by an rvalue expression). That means it refers to a temporary, or a variable that's being explicitly moved from (using `std::move` or equivalent), so your function can assume it's OK to move from it. – Mike Seymour Feb 12 '15 at 17:34
  • *"If you can perform any operation on an rvalue reference"* I suggest you be careful with the terminology here: A *name* that refers to an lvalue-reference **variable** has, as an expression, semantics identical to a *name* that refers to an rvalue-reference **variable**. In other words, the semantics of a name as an expression are independent from the referenceness of the variable the name refers to. Something like `static_cast(42)` is still an rvalue-expression, whereas let `int x = 42;` then `static_cast(42)` is an lvalue-expression. – dyp Feb 12 '15 at 18:05
  • I think the subtle thing here is, lvalue/rvalue is something beyond type, it's another attribute of an "object", and rvalue is not determined simply by declaration, but by implicitly creating temporary object or calling std::move. – TingQian LI Nov 15 '18 at 01:38

4 Answers4

66

Is bar an rvalue or an lvalue?

The question answers itself. Whatever has a name is an lvalue(1). So bar is an lvalue. Its type is "rvalue reference to string", but it's an lvalue of that type.

If you want to treat it as an rvalue, you need to apply std::move() to it.


If you can perform any operation on an rvalue reference that you can on an lvalue reference what is the point in differentiating between the two with the "&&" instead of just an "&"?

This depends on your definition of "perform an operation." An lvalue reference and a (named) rvalue reference are pretty much identical in how you can use them in expressions, but they differ a lot in what can bind to them. Lvalues can bind to lvalue references, rvalues can bind to rvalue references (and anything can bind to an lvalue reference to const). That is, you cannot bind an rvalue to an lvalue reference, or vice versa.

Let's talk about a function parameter of rvalue reference type (such as your bar). The important point is not what bar is, but what you know about the value to which bar refers. Since bar is an rvalue reference, you know for certain that whatever bound to it was an rvalue. Which means it's bound to be destroyed when the full expression ends, and you can safely treat it as an rvalue (by stealing its resources etc.).

If you're not the one doing this directly to bar, but just want to pass bar on, you have two options: either you're done with bar, and then you should tell the next one who receives it that it's bound to an rvalue—do std::move(bar). Or you'll need to do some more things with bar, and so you do not want anyone inbetween stealing its resources from under you, so just treat it as an lvalue—bar.

To summarise it: The difference is not in what you can do with the reference once you have it. The difference is what can bind to the reference.


(1) A good rule of thumb, with minor exceptions: enumerators have names, but are rvalues. Classes, namespaces and class templates have names, but aren't values.

Angew is no longer proud of SO
  • 167,307
  • 17
  • 350
  • 455
  • @Agnew Which would trigger the second part of my question, what's the point of calling it an rvalue reference then? Why not just call rvalue and lvalue references: references? – Jonathan Mee Feb 12 '15 at 17:01
  • @JonathanMee Tried to explain. Please let me know if it's clearer or needs more. – Angew is no longer proud of SO Feb 12 '15 at 17:17
  • 1
    @JonathanMee it is a reference to something that can be safely treated as an rvalue (that is, you can call `std::move` on it, and you don't risk invalidating an object that might be expected elsewhere to stay valid). – jalf Feb 12 '15 at 17:25
  • @jalf You're saying that this is just a hint to the programmer, that he could `move` it if he wants to? I just don't understand the point of differentiating between an rvalue and lvalue reference then. You can `move` lvalues too, you just need to know what you are doing. – Jonathan Mee Feb 12 '15 at 17:28
  • 3
    @JonathanMee No, it's not just a hint to the programmer. It's a *hard rule* on the *outer side.* The difference is not in what you can do with the reference *once you have it.* The difference is what *can bind to* the reference. – Angew is no longer proud of SO Feb 12 '15 at 17:31
  • @Angew Thanks for sticking with me on this. After you said that it all clicked into place. – Jonathan Mee Feb 12 '15 at 17:34
  • 1
    *"Whatever has a name is an lvalue."* Pedantic remark: Enumerators are rvalues. Is there a simple rule that reconciles this? (e.g. "Names of variables and functions are lvalues") – dyp Feb 12 '15 at 18:08
  • @dyp There always has to be an annoying details, right? Amended. – Angew is no longer proud of SO Feb 12 '15 at 18:19
  • 1
    @dyp: The official definition is simple enough: "An *lvalue* designates a function or object". Names are irrelevant: not all functions and objects have names, and not all named things are *lvalues*. – Mike Seymour Feb 13 '15 at 16:36
  • @MikeSeymour Based on this definition, how is `std::string{}` not an lvalue? – Angew is no longer proud of SO Feb 13 '15 at 16:38
  • @Angew: It doesn't designate an existing object, but creates a temporary object. A conversion that creates a temporary is defined to be an *rvalue*. – Mike Seymour Feb 13 '15 at 16:43
  • @MikeSeymour The form of that statement (which is in a part of the Standard that I consider rather informative than normative) is "lvalue => designates function/object". What I'd like to have is some relation "X => lvalue". E.g. what is the difference wrt "designating an object" between `f().member` vs `g().member` where `class_type& f()` vs `class_type&& g()` (or even `class_type g()`)? – dyp Feb 13 '15 at 17:31
  • Regarding the first paragraph: the type of *the expression `bar`* is `std::string`; the type of *the entity `bar`* is `std::string&&`. Seeing as you also say "it's an lvalue", you seem to be talking about the expression, and therefore its type is in fact just `std::string`. (Expressions never have reference type) – M.M Jul 05 '18 at 05:04
  • So, @SamWong had an interesting comment: https://stackoverflow.com/a/51177761/2642059 Is there an answer to why `bar` would report being an lvalue reference? – Jonathan Mee Jul 09 '18 at 12:16
13

Is bar an rvalue or an lvalue?

It's an lvalue, as is any expression that names a variable.

If you can perform any operation on an rvalue reference that you can on an lvalue reference what is the point in differentiating between the two with the "&&" instead of just an "&"?

You can only initialise an rvalue reference with an rvalue expression. So you can pass a temporary string to your function, but you can't pass a string variable without explicitly moving from it. This means your function can assume the argument is no longer wanted, and can move from it.

std::string variable;
foo(variable);            // ERROR, can't pass an lvalue
foo(std::move(variable)); // OK, can explicitly move
foo("Hello");             // OK, can move from a temporary
Mike Seymour
  • 249,747
  • 28
  • 448
  • 644
  • So you're saying the point of an rvalue reference is to inform the compiler that when it uses that value in a constructor it can use the move constructor? Wouldn't this cause problems if I initialize twice? Say `string temp1(bar), temp2(bar);` – Jonathan Mee Feb 12 '15 at 17:05
  • 3
    @JonathanMee: No, the point of an rvalue reference is that it can only bind to an rvalue. In that example, `bar` is an lvalue, so both `temp` variables are initialised with the copy constructor (taking an lvalue reference), not the move constructor (taking an rvalue reference) - preventing any problems which might be caused if the first constructor moved from `bar`. – Mike Seymour Feb 12 '15 at 17:30
0

The expression bar is an lvalue. But it is not an "rvalue reference to string". The expression bar is an lvalue reference. You can verify it by adding the following line in foo():

cout << is_lvalue_reference<decltype((bar))>::value << endl; // prints "1" 

But I agree with the rest of the explanation of Angew.

An rvalue reference, after it has been bound to an rvalue, is an lvalue reference. Actually it is not just for function parameters:

string&& a = "some string";
string&& b = a; // ERROR: it does not compile because a is not an rvalue

If you can perform any operation on an rvalue reference that you can on an lvalue reference what is the point in differentiating between the two with the "&&" instead of just an "&"?

An rvalue reference allows us to do some "lvalue operations" before the rvalue expires.

Sam Wong
  • 1
  • 1
  • To be clear: saying "the type of bar" is ambiguous as it stands. The token `bar` could syntactically either be an expression, or not an expression. If it is syntactically an expression, then the type is `std::string` (not a reference). However if it is syntactically the operand of `decltype(id-expression)` then the type is `std::string&&`. – M.M Jul 05 '18 at 05:06
-2

Here in this case named rvalue references are lvalues, while if the input type is const named rvalue references then it will be rvalue, here is a simple example:

#include <string>
#include <iostream>

void foo(const std::string&& bar) { 
    std::string* temp = &bar; // compile error, can't get address
    std::cout << *temp << " @:" << temp << std::endl;
}

int main()
{
    foo("test");
    return 0;
}

Hope this is useful.

Kehe CAI
  • 1,161
  • 12
  • 18
  • So this does seem to be true. Can you provide a source on this? – Jonathan Mee Dec 05 '17 at 13:46
  • https://codesynthesis.com/~boris/blog//2012/07/24/const-rvalue-references/,hope this link is useful – Kehe CAI Dec 06 '17 at 06:45
  • 2
    This is wrong. `bar` is still an lvalue here; the problem is that `&bar` has type `const std::string *`, so this cannot be assigned to `std::string*` which would discard the const qualifier. If you change the code to `const std::string* temp =` then it will compile just fine. – M.M Jul 05 '18 at 05:07