3

I have a little C++ question.

On the first pages of Effective Modern C++, there is an example:

class Widget {
public:
    Widget(Widget&& rhs);
};

Also, there is a comment: 'rhs is an lvalue, though it has an rvalue reference type'.

I just understood nothing, to be honest. What does it mean 'rhs is an lvalue, but it's type is rvalue reference'?

DanielKO
  • 4,422
  • 19
  • 29
str14821
  • 243
  • 1
  • 10

2 Answers2

7

Keep in mind that there are two distinct things here:

  • One is related to the type of variables: there are two types of references: lvalue references (&) and rvalue references (&&).

    This determines what the function preferentially accepts and is always "obvious" because you can just read it from the type signature (or use decltype).

  • The other is a property of expressions (or values): an expression can be an lvalue or an rvalue (actually, it's more complicated than that...).

    This property is not manifest in the code directly (but there is a rule of thumb, see below), but you can see its effects in the overload resolution. In particular,

    • lvalue arguments prefer to bind to lvalue-reference parameters, whereas
    • rvalue arguments prefer to bind to rvalue-reference parameters.

These properties are closely related (and in some sense "dual" to each other), but they don't necessarily agree with each other. In particular, it's important to realize that variables and expressions are actually different things, so formally speaking they aren't even comparable, "apples to oranges".


There is this rule in C++ that, even though you have declared rhs to be an rvalue reference (meaning that it will preferentially match arguments that are rvalues), within the block of move constructor, the variable rhs itself will still behave as an lvalue, and thus preferentially match functions that accept lvalue references.

void test(Widget&&) { std::cout << "test(Widget&&): called\n"; }
void test(Widget&)  { std::cout << "test(Widget&): called\n"; }

Widget::Widget(Widget&& rhs) {
    // here, `rhs` is an lvalue, not an rvalue even though
    // its type is declared to be an rvalue reference

    // for example, this will match `test(Widget&)`
    // rather than the `test(Widget&&)` overload, which may be
    // a bit counter-intuitive
    test(rhs);

    // if you really want to match `test(Widget&&)`
    // you must use `std::move` to "wrap" the variable
    // so that it can be treated as an rvalue
    test(std::move(rhs));
}

The rationale for this was to prevent unintended moves within the move constructor.

The general rule of thumb is: if the expression has a name (i.e. consists of a single, named variable) then it's an lvalue. If the expression is anonymous, then it's an rvalue. (As dyp noted, this is not technically correct -- see his comment for a more formal description.)

Community
  • 1
  • 1
Rufflewind
  • 8,545
  • 2
  • 35
  • 55
  • 2
    And you use `std::move()` to get rid of its name (turn it into an rvalue). – David G Nov 16 '14 at 22:44
  • 1
    *"even though you have declared `rhs` to be an rvalue reference [...], the variable `rhs` itself will still behave as an lvalue"* I think in Standardese, one would say *even though you have declared a variable/parameter with the name `rhs` and the type 'rvalue reference to `Widget`', an expression referring to that variable is an lvalue-expression.* – dyp Nov 16 '14 at 23:33
  • 1
    Similarly: *"if the expression has a name"* expressions do not have names. Expressions can contain names / "names" (identifiers as expressions, i.e. id-expressions) can be expressions. An identifier-expression is an lvalue, unless it refers to an enumerator. (But I'm nitpicking, +1) – dyp Nov 16 '14 at 23:41
  • 1
    I'm sort of quoting what someone else has said, but yes it's technically incorrect to equate expressions and variables. I didn't want to bog down the answer with too much Standardese though :) – Rufflewind Nov 17 '14 at 02:12
2

Short and simple explanation :P

Widget(Widget&& rhs);

is a move constructor. It will accept a rvalue as a parameter. Inside the definition of the move constructor, you can refer to the other Widget using the name rhs, therefore it is an lvalue.

leorex
  • 2,058
  • 1
  • 14
  • 15