Need Meyers Effective C++ Widget rvalue example explanation

314 views Asked by At

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'?

2

There are 2 answers

4
Rufflewind On BEST ANSWER

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.)

0
leorex On

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.