# [C++] Your Rvalue Isn't Meant to Be the Rvalue ###### tags: `C++` `Compiler` `Parser` `Software` A small talk about the *rvalue reference*, which itself is an *lvalue* that refers to a *rvalue* object. Intriguingly, *rvalue reference* can't be used as an *rvalue* before explicitly casting to *rvalue*, or *rvalue reference*. :::info For example, why compiling the following code will get a `cannot bind rvalue reference of type ‘int&&’ to lvalue of type ‘int’` error? ```cpp= void f(int&& i) {} void ff(int&& i) { f(i); } // rvalue reference would become an lvalue // should be f((int &&) i) or f(std::move(i)) ``` What's more (What's worse), why a template function with a `T&&` (something like an "rvalue reference") argument, can accept an lvalue? What are forwarding references for? ```cpp= template<typename T> void f(T&& t) {} template<typename T> void ff(T&& t) { f(t); } // rvalue reference would become an lvalue // should be f((T&&) t) or f(std::forward<T>(t)) int main() { int i; f(i); // the lvalue argument (int) becomes lvalue reference (int&) // so this is legal, what??? } ``` (Detailed answer is discussed in the [Rvalue Reference Question](#Rvalue-Reference-Question) below.) ::: ## TL;DR Since C++11, new expression reference type categories have been introduced: ![image](https://hackmd.io/_uploads/HkYrwzA_xl.png) > C\++98 value category: ==**lvalue**== & ==**rvalue**==. > C++11 just subdivides ==**rvalue**== into: **xvalue (`T&&`)** + **prvalue (`T&`)**. :::info **Add new ==move semantics== (xvalue) to the expression reference type.** - **lvalue (`T`):** for object basic assignment. - **rvalue:** - **prvalue (normal rvalue `T&`):** for copy semantics assignment. - **xvalue (unamed rvalue reference `T&&`):** for move semantics assignment. ::: ## C++ Value Categories *"One of the properties" of the expressions in C++: expression value types, expression reference types, ...* For example, what are the value/reference types of these expressions: ```cpp= int (i); // expression is lvalue (i); // expression is lvalue (++i); // expression is lvalue ((int &) i); // expression is lvalue (i++); // expression is prvalue template<int (i)> // expression is prvalue (42); // expression is prvalue ([](){}); // expression is prvalue ((int &&) i); // expression is xvalue (std::move(i)); // expression is xvalue ``` > This is evaluated on type checking of semantic analysis performed by the parser. - [Value categories - cppreference](https://en.cppreference.com/w/cpp/language/value_category.html) - [What are rvalues, lvalues, xvalues, glvalues, and prvalues? - StackOverflow](https://stackoverflow.com/questions/3601602/what-are-rvalues-lvalues-xvalues-glvalues-and-prvalues) - [Reference declaration - cppreference.com](https://en.cppreference.com/w/cpp/language/reference) ### Reference Types *Expression E* can be one of the following reference type: 1. **Lvalue (left/locator-value)** ⟺ *expression E* refers to an entity referencing to an *identity (e.g., address, name, 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 *expression E*: - The **function** that returns *rvalue reference*. ```cpp int&& f(); ``` - The **cast** to *rvalue reference* which refers to object's actual value entity. ```cpp (int &&) 7; ``` ```cpp static_cast<int&&>(7); // equivalent to the above one ``` ```cpp std::move(7); // equivalent to the above one ``` - The **pointer-to(-xvalue)-member expression** such as `<xvalue>.*member_ptr` will yield an xvalue as well. ```cpp struct A { std::string s; int i = 10; void func() {} } a; // void (A::*f_ptr)() = &A::func; // int A::*i_ptr = &A::i; /* i_ptr: pointer to the member A::i of struct A * * like C++ version of C offsetof(A, i) for struct: * (size_t)((char*)&((A*)(0))->i - (char*)0) * * equivalent to (int)(char *) &A::i * (though C++ not allowed pointer-to-member casting to raw pointer, * making this casting UB, * kinda member-access abstraction.) */ auto s_ptr = &A::s; std::string s = std::move(a).*s_ptr; // here, (<xvalue>.*s_ptr) will yield an xvalue. ``` Refs: - [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) - 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 ``` 3. **PRvalue (pure-rvalue)** ⟺ *expression E* belongs neither to *lvalue* nor to *xvalue* (i.e. non-locatable & movable temporaries in expressions or literals). ```cpp a + b; ``` ### Conceptual Reference Type 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". - [Binding - MDN Web Docs](https://developer.mozilla.org/en-US/docs/Glossary/Binding) - **GLvalue (generalized-lvalue)** ⟺ *E* refers to an ==named *representative* entity== that has an *identity (e.g., address, name, alias)*. | is *GLvalue* (↓) \ *Rvalue* (→) ? | yes (**Can be moved**) | no (**Cannot be moved**) | |:---------------------------------:|:----------------------:|:------------------------:| | yes (**Has identity**) | *Xvalue* | *Lvalue* | | no (**No identity**) | *PRvalue* | N/A | ![image](https://hackmd.io/_uploads/ByLs7EuuA.png =500x) ## Rvalue Reference Question Ok, with all this prerequisite background knowledge. Let's move back to the original question: Why the following C++ code causes `the "rvalue" can't bind to the "lvalue"` error: ```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); } ``` We can get the answer at [std::move - cppreference.com](https://en.cppreference.com/w/cpp/utility/move), but I think it is more vivid to see the below exellent explanation 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. but, wait ... :::warning **[Template Auto Argument Type Deduction] Forwarding References (Universal References)** *Perfect Forwarding (e.g., preserving `volatile const int &`) & Reference Collapsing Rules* - [Template argument deduction - cppreference](https://en.cppreference.com/w/cpp/language/template_argument_deduction.html#Other_contexts) - [Reference declaration - cppreference](https://en.cppreference.com/w/cpp/language/reference.html): Reference collapsing ```cpp= template<typename T> void ff(T&& t) { // forwarding reference t.f(); // rvalue reference (lvalue) or lvalue reference (lvalue) ((T&&) t).f(); // forwarding reference (NOT rvalue reference) // equivalant to (std::forward<T>(t)).f(); } int main() { struct S { void f() & { std::cout << "&\n"; } void f() && { std::cout << "&&\n"; } } s; ff(s); ff((S&&) s); } ``` with `decltype(auto)` for returned values with perfect forwarding (auto type deduction): ```cpp= template<typename T, typename... Args> decltype(auto) init(T t, Args&&... args) { return t { std::forward<Args>(args)... }; } ``` with in-struct template auto argument type deduction: ```cpp= template<typename T> struct A { template<typename F> A(T&&, F&&); // T&&: rvalue reference // F&&: forwarding reference }; ``` `T&&` arguments with `const` (i.e., not cv-unqualified): ```cpp= template<typename T> void f(const T&&); // const T&&: rvalue reference ``` The `T&&` ++(cv-unqualified) parameters++ in a template function are considered as a *forwarding references* for perfect forwarding semantics, **NOT rvalue references anymore**: - if `T` is an rvalue: `T&&` would collapse to an rvalue reference (`T&&`). - if `T` is an lvalue: `T&&` would collapse to an lvalue reference (`T&`). > Also, using template's forwarding reference means nothing is passed by value! > (Even `int i` will implicitly cast to `int&`!) ::: :::info What if I just want template functions with rvalue reference arguments? > Use SFINAE test! ```cpp= template<typename T> requires(std::is_rvalue_reference_v<T &&>) void func(T&&) {} ``` or `delete` :arrow_right: For more details, see: [Modern C++ Programming - shibarashinu](https://hackmd.io/@shibarashinu/H1i8HFH0A). ::: ## Dangling Reference However, such complex reference type mechanisms in C++ sometimes may cause dangerous dangling references due to bad access to invalid, out-of-lifetime references (e.g, out of scope, abiding by RAII rules), leading programs to undefined behavior. ```cpp= std::string& f() { std::string s = "Example"; return s; // exits the scope of s, // its destructor is called and its storage deallocated } std::string& r = f(); // dangling reference std::cout << r; // undefined behavior: reads from a dangling ref auto s = f(); // undefined behavior: copy-initializes from a dangling ref ``` ```cpp= std::vector<std::string> vec; std::string str = "example"; vec.push_back(std::move(str)); // since here, str's value is unspecified std::cout << str; // undefined behavior: reads from a value-moved variable ```