Rvalue Reference Is Treated as an Lvalue

Rvalue Reference is Treated as an Lvalue?

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.

Why is a named rvalue reference an lvalue expression?

Per [expr.prim.id.unqual] (8.1.4.1 Unqualified names):

[...] The expression is an lvalue if the entity is a function,
variable, or data member and a prvalue otherwise; it is a bit-field if
the identifier designates a bit-field ([dcl.struct.bind]).

Per [basic]/6:

A variable is introduced by the declaration of a reference other
than a non-static data member or of an object. The variable's name, if
any, denotes the reference or object.

The declaration

int&& ref2 = std::move(x);

is a "declaration of a reference other than a non-static data member." Therefore, the entity denoted by ref2 is a variable. So the expression ref2 is an lvalue.

Is Widget&& rhs an lvalue or an rvalue reference

Type: The type of the argument, in both cases, is "rvalue reference to Widget".

Value category: The name of the argument, in both cases, is an lvalue expression.

There is no difference.

  • Further reading: Rvalue Reference is Treated as an Lvalue?

Why is rvalue references considered safer than lvalue references?

The advantage of rvalue references over lvalue references is that with rvalue references you know that the object referred to is an rvalue. Thus you know that you are allowed to manipulate it without damaging other data.

If non-const lvalue references were allowed to refer to rvalues, you would never know if the object referred to was temporary or not. Consider, for example these two constructors

class A {
public:
A(A& src) // Copy constructor
{
// Create a copy of src, but be sure to leave src intact
}

A(A&& src) // Move constructor
{
// Create a copy of src. If necessary, you can steal some
// of src's resources (dynamic memory, open files). It
// won't miss it
}
};

The performance advantage you can get in the second version cannot be achieved in the first, even if lvalue references were allowed to refer to rvalues.

Why when r-value reference is being assigned to is considered as an l-value reference?

You write this:

even though the type of value is int&&, when it's being used as a value [and not an expression(as with decltype)], it suddenly becomes an l-value.

One high-level way to think about lvalues and rvalues is that lvalues have names and can be assigned to. value obviously has a name, so it shouldn't be surprising that it's an lvalue.

One way to think of std::move() or your static cast is that it not only casts its argument to the correct type, but it also produces an rvalue expression.

Thinking about your two functions s and f:

  • Presumably, your function s takes an rvalue reference because you want to operate on rvalues, e.g. steal their resources with a move operation.
  • In such a function, you might want to call functions on value that either (i) steal its resources, or (ii) treat it as an lvalue and do not steal its resources.
  • The language lets you call std::move() for case (i), to tell f that f may steal resources.
  • The language lets you pass value to functions without std::move(), as an lvalue, for case (ii), so you can call functions without worrying about that. This might be desirable if you want s to steal resources later on.

This question is pretty similar: Rvalue Reference is Treated as an Lvalue?

Why rvalue reference as return type can't be initialization of non-const reference?

I know that an rvalue reference is an lvalue.

You're talking about two different things: type and value category. e.g.

int&& ri = 0; // ri's type is rvalue reference (to int)
// ri's value category is lvalue; it's a named variable.

Given your 1st sample, what fun() returns is an xvalue, which belongs to rvalues.

The following expressions are xvalue expressions:

  • a function call or an overloaded operator expression, whose return type is rvalue reference to object, such as std::move(x);

then,

int &a = fun(); // fails; lvalue-reference can't bind to rvalue

In the 2nd sample, what fun() returns is an lvalue,

The following expressions are lvalue expressions:

  • a function call or an overloaded operator expression, whose return type is lvalue reference, such as std::getline(std::cin, str),
    std::cout << 1, str1 = str2, or ++it;

then

int & a=fun(); // fine; lvalue-reference could bind to lvalue

In the 3rd sample,

decltype(fun()) b = 1; // the return type of fun() is rvalue-reference;
// this has nothing to do with the value category of its return value
// b's type is rvalue-reference too, btw its value category is lvalue

In the 4th sample,

int &&a = 1; // fine; rvalue-reference could bind to rvalue
// a's type is rvalue-reference, its value category is lvalue
int &b = a; // fine; lvalue-reference could bind to lvalue
// b's type is lvalue-reference, its value category is lvalue

Aren't forwarding references deduced as r-value references?

Types and value categories are two independent properties of expression.

Each C++ expression (an operator with its operands, a literal, a variable name, etc.) is characterized by two independent properties: a type and a value category. Each expression has some non-reference type, and each expression belongs to exactly one of the three primary value categories: prvalue, xvalue, and lvalue.

The type of x is int&&, but x is the name of variable and x is an lvalue expression itself, which just can't be bound to int&& (but could be bound to const int&).

(emphasis mine)

The following expressions are lvalue expressions:

  • the name of a variable, a function, a template parameter object (since C++20), or a data member, regardless of type, such as std::cin or std::endl. Even if the variable's type is rvalue reference, the expression consisting of its name is an lvalue expression;

That means when function has both lvalue-reference and rvalue-reference overloads, value categories are considered in overload resolution too.

More importantly, when a function has both rvalue reference and lvalue reference overloads, the rvalue reference overload binds to rvalues (including both prvalues and xvalues), while the lvalue reference overload binds to lvalues:

If any parameter has reference type, reference binding is accounted for at this step: if an rvalue argument corresponds to non-const lvalue reference parameter or an lvalue argument corresponds to rvalue reference parameter, the function is not viable.

std::forward is used for the conversion to rvalue or lvalue, consistent with the original value category of forwarding reference argument. When lvalue int is passed to foo, T is deduced as int&, then std::forward<T>(x) will be an lvalue expression; when rvalue int is passed to foo, T is deduced as int, then std::forward<T>(x) will be an rvalue expression. So std::forward could be used to reserve the value category of the original forwarding reference argument. As a contrast std::move always converts parameter to rvalue expression.

Why the rvalue reference parameter cannot be passed between functions directly?

str is a named rvalue reference, which is treated in the language as an lvalue. rvalues are only xvalues or prvalues, and str is neither.

A note from the standard, on the xvalue rules:

In general, the effect of this rule is that named rvalue references are treated as lvalues and unnamed rvalue references to objects are treated as xvalues; rvalue references to functions are treated as lvalues whether named or not.

struct A {
int m;
};
A&& operator+(A, A);
A&& f();

A a;
A&& ar = static_cast<A&&>(a);

The expressions f(), f().m, static_­cast<A&&>(a), and a + a are xvalues. The expression ar is an lvalue.



Related Topics



Leave a reply



Submit