21

I was wondering about a c++ behaviour when an r-value is passed among functions.

Look at this simple code:

#include <string>

void foo(std::string&& str) {
  // Accept a rvalue of str
}

void bar(std::string&& str) {
  // foo(str);          // Does not compile. Compiler says cannot bind lvalue into rvalue.
  foo(std::move(str));  // It feels like a re-casting into a r-value?
}

int main(int argc, char *argv[]) {
  bar(std::string("c++_rvalue"));
  return 0;
}

I know when I'm inside bar function I need to use move function in order to invoke foo function. My question now is why?

When I'm inside the bar function the variable str should already be an r-value, but the compiler acts like it is a l-value.

Can somebody quote some reference to the standard about this behaviour? Thanks!

xaxxon
  • 19,189
  • 5
  • 50
  • 80
BiagioF
  • 9,368
  • 2
  • 26
  • 50

3 Answers3

13

str is a rvalue reference, i.e. it is a reference only to rvalues. But it is still a reference, which is a lvalue. You can use str as a variable, which also implies that it is an lvalue, not a temporary rvalue.

An lvalue is, according to §3.10.1.1:

An lvalue (so called, historically, because lvalues could appear on the left-hand side of an assignment expression) designates a function or an object. [ Example: If E is an expression of pointer type, then *E is an lvalue expression referring to the object or function to which E points. As another example, the result of calling a function whose return type is an lvalue reference is an lvalue. —end example ]

And an rvalue is, according to §3.10.1.4:

An rvalue (so called, historically, because rvalues could appear on the right-hand side of an assignment expression) is an xvalue, a temporary object (12.2) or subobject thereof, or a value that is not associated with an object.

Based on this, str is not a temporary object, and it is associated with an object (with the object called str), and so it is not an rvalue.

The example for the lvalue uses a pointer, but it is the same thing for references, and naturally for rvalue references (which are only a special type of references).

So, in your example, str is an lvalue, so you have to std::move it to call foo (which only accepts rvalues, not lvalues).

Rakete1111
  • 47,013
  • 16
  • 123
  • 162
  • Thanks for the explanation. Why didn’t you use the term xvalue to make it less confusing? – jaques-sam Feb 17 '21 at 11:30
  • @DrumM where should I have mentioned xvalues? The OP is only confused on why the rvalue reference isn't an actual rvalue. – Rakete1111 Feb 17 '21 at 11:34
  • It's mainly okay, maybe "temporary rvalue"? But it doesn't really matter as it's all about lvalues within the function. – jaques-sam Feb 18 '21 at 17:05
7

The "rvalue" in "rvalue reference" refers to the kind of value that the reference can bind to:

  • lvalue references can bind to lvalues
  • rvalue references can bind to rvalues
  • (+ a bit more)

That's all there's to it. Importantly, it does not refer to the value that get when you use the reference. Once you have a reference variable (any kind of reference!), the id-expression naming that variable is always an lvalue. Rvalues occur in the wild only as either temporary values, or as the values of function call expressions, or as the value of a cast expression, or as the result of decay or of this.

There's a certain analogy here with dereferencing a pointer: dereferencing a pointer is always an lvalue, no matter how that pointer was obtained: *p, *(p + 1), *f() are all lvalues. It doesn't matter how you came by the thing; once you have it, it's an lvalue.

Stepping back a bit, maybe the most interesting aspect of all this is that rvalue references are a mechanism to convert an rvalue into an lvalue. No such mechanism had existed prior to C++11 that produced mutable lvalues. While lvalue-to-rvalue conversion has been part of the language since its very beginnings, it took much longer to discover the need for rvalue-to-lvalue conversion.

Kerrek SB
  • 464,522
  • 92
  • 875
  • 1,084
4

My question now is why?

I'm adding another answer because I want to emphasize an answer to the "why".

Even though named rvalue references can bind to an rvalue, they are treated as lvalues when used. For example:

struct A {};

void h(const A&);
void h(A&&);

void g(const A&);
void g(A&&);

void f(A&& a)
{
    g(a);  // calls g(const A&)
    h(a);  // calls h(const A&)
}

Although an rvalue can bind to the a parameter of f(), once bound, a is now treated as an lvalue. In particular, calls to the overloaded functions g() and h() resolve to the const A& (lvalue) overloads. Treating a as an rvalue within f would lead to error prone code: First the "move version" of g() would be called, which would likely pilfer a, and then the pilfered a would be sent to the move overload of h().

Reference.

Howard Hinnant
  • 206,506
  • 52
  • 449
  • 577