R-Value References
Created: 25th May 2026
Updated: 25th May 2026
A quick recap of L-value references. L-value references are the main way for functions to change objects that are defined in scope of the function. Constant L-value (CLV) references allow functions to refer to said objects without giving permission to change those objects. However, a downside of the syntax CLV references is that when passing into a function it is not known what the object being passed in is being processed as. It could be changeable or not and it is not quite known unless a programmer steps into the function.
A rule of thumb for passing arguments is that where possible or reasonable, pass as reference or in-out pointer (essentially pass-by-pointer but the term 'pass-by pointer' is wrong). However, you can pass-by value if the object in question is smaller or equal to 16 bytes, potentially 32 bytes but solely depends on the program and the requirements of optimisation and specifications. Some more rules to follow are, 1. pass-by-const reference when no modifcation is required, 2. pass-by-ref ONLY WHEN NEEDED, 3. Pass a pointer if 'no object' is a valid alternative.
R-value references are not the easiest to wrap your head around so I'll try my best to keep it simple and concise. The first thing to bring up is the idea of R-value expressions, which are values that are temporary and used when values need to "consumed/stolen" by another expression. Essentially, the data that a R-value expression generates is likely to be used by another value within the scope it exists in. An important thing to note, when an R-value expression is named it becomes an L-value expression within scope, meaning that it can modified or assigned to. It is possible to assign values to a function that returns R-values but it is rather pointless unless you are chain assigning it to a named value. But this may lead undefined behaviour if not managed properly. For R-value strings, you can modify individual characters.
As mentioned, you can assign to R-value expressions. This means that it is possible to assign to an operated-expression, i.e. Addition, Subtraction and etc., assuming that the expression is non-const. R-value expressions can not be assigned to by L-values and by logic cannot be bounded to by constant L-value references. However, it is possible to use R-values to be assigned to a constant reference.
The most important motivation for using R-value expressions and refernces are for Move Semantics, or the idea of "stealing/moving" resources. Usually, classes that are made with a copy constructor and copy-assignment would just make a copy of the right-hand side value and then std::swap with said copy. This would make sense if the right-hand side value was a L-value but if it is an R-value or temporary value, it wouldn't make sense to make a copy of something if it's resources and what it holds is not going to be used by anything else and be returned to the resource manager. So instead, by using Move Semantics, specifically having a move constructor and move-assignment we can "move" the resouces from the R-value to the assigned or constructed object, instead of making a copy.
This next part is quirk behaviour of R-value references which I touched on earlier which is the idea of R-value references being L-value expressions. This idea is mostly used in functions that want to read a value that will evaporated once out of scope. Essentially, while we still have the thing existing, we might want to read a particular value that this R-value reference object may have and use it for something else.
Final section, I promise. With all this laid out, I should also mention the concept of "Rule of Five" or "Rule of Zero". When defining a class or struct, if any of these 5 are explicity defined, destructor, copy, copy-assignment, move, move-assignment, all the others must also be defined. Previously, it would be the "Rule of Three", where destructors, copy and copy-assignment had to be implemented if any of the three are implemented. However, when optimising code, it is best to have Move Semantics otherwise several partially used copies will be generated leading to a slower program. The "Rule of Zero" is the direct opposite. If your class or struct had no requirement of managing resources such as raw pointers, it is best to use C++ Containers, such as std::vector, and stick to just implementing base constructors as it avoids the hassle of writing 5 other functions.
Thank you for reading this post. I'm sure I got a number of things wrong but this is the way I best understand R-value expressions and references. If you are learning R-values from this post, my recommendation is just to try things out in a compiler. If your foundational understanding of Copy Semantics is strong, then Move Semantics wouldn't be too hard to understand. Anyways, thank you for reading and see you in the next post.
Previous Post Next Post