# [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.
:::