# [C++] Your Rvalue Isn't Meant to Be the Rvalue ###### tags: `C++` `Compiler` `Parser` `Semantic Analysis` `Type Representation` A small talk about the rvalue reference, which is a lvalue variable that represents the rvalue object. Intriguingly, it can't be used as an rvalue until explicitly casting to rvalue. > **TL;DR** > 1. Since C++11, new expression categories have been introduced: ![image](https://hackmd.io/_uploads/rJa_74OdC.png =300x) > 2. ***Named Rvalue Reference*** to objects are treated as ***lvalue***; ***Unnamed Rvalue Reference*** to objects are treated as ***xvalue***; ***Rvalue References to functions*** are treated as ***lvalues*** whether named or not. > 3. **Lvalue vs. Rvalue:** > - If you can take the address of an expression, the expression is an *lvalue*. > - If the type of an expression is an *lvalue reference* (e.g., T& or const T&, etc.), that expression is an *lvalue*. > - Otherwise, the expression is an *rvalue*. Conceptually (and typically also in fact), *rvalues* correspond to temporary objects, such as those returned from functions or created through implicit type conversions. Most literal values (e.g., 10, 5.3) are also *rvalues*. :::success Before diving into the problem, let's review the category of C++ expression types first: #### Primary Category ***Expression E ∈*** 1. **Lvalue (left/locator-value)** ⟺ *E* refers to an entity referencing to an *identity (address, name or alias)* that makes it accessible outside of *E*. ```cpp int int_var_locator; ``` - **Modifiable Lvalue:** Can't have an array type, an incomplete type, or a type with the const attribute or with const members. > For example, if ptr is a pointer to a storage region, then *ptr is a modifiable l-value that designates the storage region to which ptr points. 2. **Xvalue (expiring-value)** ⟺ any of the following expressions: - **The function** that returns *rvalue reference*. ```cpp int&& f(); ``` - **The cast** to *rvalue reference* to object type. ```cpp static_cast<int&&>(7); // same as int&&(7) ``` ```cpp std::move(7); // equivalent expression ``` - **The accessing class member expression** of non-static data member of non-reference type that is *lvalue*. ```cpp struct A { int i; }; A&& f() { return A(); } f().i; // 1. f() is lvalue // 2. A::i is a non-static data member of non-reference type // => f().i is xvalue, an access expression to A::i ``` - **The pointer-to-member expression** where the 1st operand is an *xvalue* & the 2nd operand is a pointer to data member. ```cpp struct A { int i; }; A a; a.i = 42; int A::*ptr = &A::i; // pointer to the member i of class A // (C++ version of struct offsetof() in C) std::move(a).*ptr; // 1. std::move(a) is xvalue // 2. *ptr is a pointer to data member // => std::move(a).*ptr is xvalue ``` More: - [What is a pointer to class data member "::*" and what is its use? - StackOverflow](https://stackoverflow.com/questions/670734/what-is-a-pointer-to-class-data-member-and-what-is-its-use) - [offsetof(3) — Linux manual page](https://man7.org/linux/man-pages/man3/offsetof.3.html) 3. **PRvalue (pure-rvalue)** ⟺ *E* belongs neither to *lvalue* nor to *xvalue* (i.e. non-locatable & movable temporaries in expressions or literals). ```cpp a + b; ``` #### Conceptual Mixed Categories - **Rvalue (right-value)** ⟺ *E* refers to an ==movable *floating* entity== stored at some address in memory (i.e. literal values) that hasn't had any identity that makes it accessible outside of *E* "yet". - **GLvalue (generalized-lvalue)** ⟺ *E* refers to an ==named *representative* entity== that has an *identity (address, name or alias)*. | is *GLvalue* \ is *Rvalue* | **Can be moved** | **Cannot be moved** | |:--------------------------:|:----------------:|:-------------------:| | **Has identity** | *Xvalue* | *Lvalue* | | **No identity** | *PRvalue* | N/A | ![image](https://hackmd.io/_uploads/ByLs7EuuA.png =500x) Refs: - [What are rvalues, lvalues, xvalues, glvalues, and prvalues? - StackOverflow](https://stackoverflow.com/questions/3601602/what-are-rvalues-lvalues-xvalues-glvalues-and-prvalues) - [L-Value and R-Value Expressions - Microsoft Learn](https://learn.microsoft.com/en-us/cpp/c-language/l-value-and-r-value-expressions?view=msvc-170) ::: :::info **Practice Question:** Why the following C++ code will cause "the rvalue can't bind to the lvalue": ```cpp= void f(int&& i) {} void ff(int&& i) {f(i);} ``` but this one doesn't: ```cpp= void f(int&& i) {} void ff(int&& i) {f((int&&)i);} ``` (Organized by ChatGPT:) #### First Case - `int&&` means that f accepts an rvalue reference. - In `ff`, the parameter `i` is of type `int&&`, but within the function body, `i` is an lvalue. This is because `i` has a name and can be referred to multiple times. When you call `f(i)`, you are trying to pass an lvalue (`i`) to a function that expects an rvalue reference. This is why you get a compilation error: an rvalue reference cannot bind to an lvalue. #### Second Case - In this version, you are explicitly casting `i` to an rvalue reference using `static_cast<int&&>(i)` (the C-style cast `(int&&)i` achieves the same effect). - This cast tells the compiler to treat `i` as an rvalue, allowing it to bind to the rvalue reference parameter of `f`. #### Modern C++ Solution In modern C++, it's recommended to use `std::move` for such casts because it makes the intention clearer: ```cpp= #include <utility> // for std::move void f(int&& i) {} void ff(int&& i) { f(std::move(i)); } ``` `std::move` effectively performs a `static_cast` to an rvalue reference, signaling that `i` can be "moved from ...", which is often what you want when dealing with rvalue references. #### Summary - **lvalues** have a name and a persistent state. - **rvalues** are temporary and typically do not have a persistent state. - **rvalue references** (`T&&`) bind to rvalues. - **lvalues** cannot be passed directly to functions expecting rvalue references. The casting (`static_cast<int&&>(i)` or `std::move(i)`) converts an lvalue (that originally was an rvalue) to an rvalue, enabling binding to rvalue references. :::