DimG
DimG

Reputation: 1781

Rvalue to lvalue conversion and "named-refs-are-lvalues" rule

There're lots of rvalue related quiestion but I didn't found answers to these exact questions.

I can't wrap my mind around "named reference is a lvalue reference" rule of thumb.

It seems really strange -- we declare the reference as an rvalue but since we have to somehow use this reference (otherwise, what's the point?), we name it and since it's named it's an lvalue after all!

Consider this code:

int&& foo(int&& arg)
{
    arg = 10;
    return arg; // ERROR, `arg` is an lvalue since it's named!
}
foo(1);

The questions are:

  1. When exactly does arg become an lvalue?
  2. What would be the type of arg if the function was void and b) and c) lines were not present?
  3. Lot's of articles (just citing first found result) say that there may be implicit lvalue to rvalue conversion but the opposite direction is not possible -- why? This example shows that arg -s converted from int&& to int& and then tries to implicitly convert int& to int&& which causes compilation error -- just the opposite behaviour! That's why we need std::move which is basicaly an explicit static_cast to rvalue type.

Upvotes: 2

Views: 746

Answers (3)

Hari
Hari

Reputation: 1801

Question 3 asks why an implicit conversion from an lvalue (whether a reference or not) to an rvalue reference is not allowed. This could be based on the constraint that rvalue references can bind only to rvalues. This constraint is essential, for example, to overload functions in order to implement copy and move constructors.

About whether an expression can have a reference type, this blog post by Scott Meyers is very useful. The quote from the latest standard draft:

If an expression initially has the type “reference to T” ([dcl.ref], [dcl.init.ref]), the type is adjusted to T prior to any further analysis. The expression designates the object or function denoted by the reference, and the expression is an lvalue or an xvalue, depending on the expression.

Thus, to be very accurate, an expression can have reference type but this reference-ness is removed for all practical purposes.

Upvotes: 0

Amir Kirsh
Amir Kirsh

Reputation: 13752

We should refine the "if it has a name it's an lvalue" rule, to the more exact form:

If it has a name, then when used as an expression it is an lvalue.

But don't we always use a variable as an expression?

Usually yes, but not necessarily. See the following function:

void foo(A&& a) {
    // decltype of the variable a - is rvalue
    static_assert(std::is_rvalue_reference_v<decltype(a)>);

    // decltype of the expression a - is lvalue
    static_assert(std::is_lvalue_reference_v<decltype((a))>);
}

Note that a doesn't just become an lvalue. If it is being used as an expression - which is a common use for a variable - then the expression is an lvalue reference.

In the code above, the decltype of a is A&& thus still an rvalue reference.

On the other hand, (a) turns the use of a into an expression in that context, thus the decltype of (a) is A& - an lvalue reference.

You can think about that as: the expression that uses the variable a, can be considered as an lvalue reference to A&& (i.e. A&& &), which with reference collapsing becomes A&.


The fact that the variable a is still an rvalue, and only the expression a is an lvalue, is not so commonly in use. Usually we just deal with a as an expression. However, it is being used when we want to forward an auto&& parameter, e.g.:

[](auto&& v){ foo(std::forward<decltype(v)>(v)); } (A{});

From C++ Reference: An expression, can be categorized into one of three options:

  • if it has an identity and cannot be moved from, it is called an lvalue expression
  • if it has an identity and can be moved from, it is called an xvalue expression
  • if it doesn't have an identity and can be moved from, it is called a prvalue ("pure rvalue") expression

Expressions that can be moved from are called "rvalue expressions". Both prvalues and xvalues are rvalue expressions. Since C++17, prvalues are no longer moved from, due to mandatory copy elision.

Expressions that have an identity are called "glvalue expressions" (glvalue stands for "generalized lvalue"). Both lvalues and xvalues are glvalue expressions.

Having an identity is considered as being possible to determine whether the expression refers to the same entity as another expression, such as by comparing addresses of the objects or the functions they identify.


In the following code we compare lvalue, prvalue and xvalue expressions:

void foo(A&& a) {
    // below we send the expression a to bar, as lvalue
    bar(a);

    // std::move(a) is an xvalue expression
    bar(std::move(a));

    // A{} is a prvalue expression
    bar(A{});
}

There is a list of expressions that may create a an xvalue (see the link below, for "What expressions create xvalues?"), but the most common two expressions for that are std::move and std::forward.

Creating a prvalue is easy, you just create an object without a name, or use an object in an expression that requires a prvalue (there is a conversion from lvalue to prvalue).


Code to play with: https://godbolt.org/z/so746KcqG


See also:

Upvotes: 3

Cubbi
Cubbi

Reputation: 47418

arg the variable has type int&& and no value category.

arg the expression (it is an expression at lines 3 and 4) has type int and value category "lvalue"

Lvalue to rvalue conversion changes the value category of an expression, without changing its type. If you write arg+1 inside the function, the lvalue expression arg of type int would undergo this conversion to produce a prvalue expression of type int, since that's what built-in + requires.

There are no "lvalue to rvalue" or reverse conversions between int& and int&& because expressions never have reference types. The error in your program is a failure to bind an rvalue reference (of type int&&) to an lvalue expression (of type int).

Upvotes: 5

Related Questions