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.