This lesson will be a short digression where we will start discussing
value categories in C++. To explain this abstract concept,
I will introduce you to a series of simple programs. It is hard to
explain what these value categories represent because they were expanded
for the purpose of implementing move semantics,
but to understand move semantics, you need to understand value categories.
So, bear with me because I'm going to try to explain this in my own way.
This simple program is declaring an integer variable and passing it as a
reference to this function. We learned that references are just like the second
identifiers used for accessing the same place in memory. So these two
identifiers, x and num, x is the same place in memory.
But what would happen if I tried to pass a simple
integer literal to this function?
This would not work because this number is not a variable,
so it doesn't necessarily have its own place in memory.
If I hover over this expression,
you can see that my editor generated a special message, and you would get a
similar one if you tried to compile this program. It says that the initial
value of reference to a non‑const must be an lvalue.
Let's ignore this lvalue for now and focus on this part that the reference
to a non‑const initial value will not work in this case.
So let's try to turn this reference into a constant reference.
If I do this,
the warning disappears. We can even try to compile this to make sure.
Both values are printed out to the terminal, which means that the
function also accepted a literal number.
When we turn this reference into a constant reference,
compiler will set aside a temporary place in memory for this number, 10, and
then let us create a reference to that place in memory.
But it will only do that if you use this const keyword
because we cannot update the value of a number.
The number itself is already a literal value, so its lifetime will be
expanded until this reference is removed from the scope.
So now this argument can accept both the values, which have an
identity like this variable, and the values, which don't have
an identity like this number 10.
These are the first two value categories that we are going
to talk about, the lvalues and prvalues.
You can use the assignment operator as a mnemonic to help you remember
which one is which. Lvalues, short for left values, are the values on the
left, in this case, the values that have identity.
Prvalues, or pure right values, are the values on the right, in this case,
this integer literal that does not have an identity.
I said that you can use this as a mnemonic tool, but you should
not rely on lvalues always being on the left and prvalues on
the right side of the statement.
A better way to differentiate these two categories is to remember that
lvalues always have a specific location in memory.
So, a simple variable is an lvalue, pointer is an lvalue, reference is an
lvalue, and also a function that returns a reference is an lvalue.
Can we use the address‑of operator on all of these expressions?
Yes, we can because all of them have a special place in memory.
Even this expression has a place in memory because it's a function,
which will first take in this non variable as a reference and then
return a reference to that same variable.
Of course, this syntax looks weird, but it works, which means that this
expression evaluates to a value that has an identity.
Prvalues are expressions, which evaluate to temporary values
that don't have a special place on the stack.
In this statement, only the number 5 is a prvalue, num variable is an lvalue.
The same can be assumed for this second statement, but this whole
expression will evaluate to a temporary number, 10.
So this is an lvalue. This is a prvalue, but the result of this
expression is also a prvalue because it only exists here, and it
doesn't have a special identity.
Same goes for the object, which is instantiated from
a specific user‑defined class.
This expression by itself is a prvalue because this instantiated object
is temporary until we assign it to something else.
Now let's get back to the first program. I made some modifications.
Now the function also brings out this simple message before
it outputs the value from the reference.
A simple reference like this one is also known as an lvalue
reference because it usually refers to lvalues, but it can also
refer to prvalues when we make it constant.
C++11 introduced major changes in the language, and one of these changes
is the existence of the so‑called rvalue references. Before C++11, we
only had two categories, lvalues and rvalues.
But since rvalue now doesn't mean the same thing it did before,
let's just agree that for now rvalue is equal to prvalue.
So an rvalue reference looks like this.
We can define it by using two ampersand characters instead of one. And
we don't have to make it constant because the whole purpose of rvalue
references is to point to temporary values like the prvalues we talked
about. But the real reason for their existence is the ability to use
them in function overloads.
I will overload this function with another function,
which will be able to accept an rvalue reference instead
of a constant lvalue reference.
The definition is the same, except for this special message.
Now, let's compile this again. As you can see, the number
variable is still passed to the first function, but the integer
literal is using the overloaded function.
Even though this first function is perfectly capable of accepting both
lvalues and prvalues, if the argument is a temporary prvalue,
compiler will always choose the overloaded function with an rvalue reference.
This is actually great because now we can define a special
function behavior for each value category.
Lvalues will always use the first function, and
prvalues will always use the second one.
Hopefully, now you know the difference between lvalues and
prvalues, and you also know what rvalue references are.
But before we learn about other value categories,
we have to know what moving means in C++, so we will continue
this discussion in the next two lessons.