Search

8.4 — Value categories (r-values and l-values)

Before we talk about the next compound type (references), we’re going to take a little detour.

In C++, every expression has two properties: type and value category.

Since expression always resolve to a single value, the type of the resultant value is the type of the expression.

You’ve already seen examples of the type property of expressions:

Both variable v1 and v2 have initializers that are simple expressions.

For v1, the compiler will determine that a division with two int operands will produce an int result, so that is the type of this expression. Via type inference, that int type will then be used as the type of v1.

For v2, the compiler will determine that a division with a double and an int operand will produce a double result (remember that arithmetic operators must have operands of matching types, so the int operand gets promoted to a double, and a floating point division is performed).

Compound expressions (those with multiple operators) work the same way -- the compiler uses the precedence and associativity rules to determine in what order the expression will evaluate. The type of the result is the type of the expression.

Note that the type of an expression can always be determined at compile time (if it couldn’t, type deduction wouldn’t work) -- however, the value of the expression may be determined at either compile time (if the expression is constexpr) or runtime (if the expression is not constexpr).

Value categories

Now consider the following program:


int main()
{
int x{};

x = 5; // valid
5 = x; // invalid

return 0;
}

One of these assignment statements is valid (assign the value 5 to variable x) and one is not (what would it mean to assign the value of x to the literal value 5?). So how does the compiler know which values can legally appear on which side of an assignment statement?

The second property of expressions is called the value category. An expression’s value category helps the compiler determine how to create, copy, and move objects during the evaluation of the expression.

Since assignments are a type of copy operation, the value category is used to determine whether a given assignment is legal.

As of C++11, C++ has 5 different kinds of value categories. In this lesson, we’ll explore a simplified view of two most common ones (l-values and r-values). The other three are used for move semantics, which we’ll cover in a future chapter.

// http://en.cppreference.com/w/cpp/language/value_category
// https://stackoverflow.com/questions/3601602/what-are-rvalues-lvalues-xvalues-glvalues-and-prvalues

l-values

An l-value (short for “locator value”, or “left value”, and sometimes written as lvalue) is an expression that refers to an object with an identifiable memory address. The name l-value came about historically because l-values were expressions that could legally appear on the left-hand side of an assignment expression. Variables are an example of an l-value, as they have a discrete memory address.

Since the introduction of constants into the language, l-values now come in two subtypes: a modifiable l-value is an l-value whose value can be modified. A non-modifiable l-value is an l-value whose value can’t be modified (typically because the l-value is const or constexpr). Clearly non-modifiable l-values are not legal targets for being assigned to, so it’s now more accurate to say that the left-hand side of an assignment must be a modifiable l-value.

We can now answer the question of why 5 = x; is not valid -- it’s because 5 is not an l-value. So what is 5?

r-values

The opposite of l-values are r-values (pronounced arr-values). R-values are defined by negation: an r-value is any expression that is not an l-value. Common r-values include literals, temporary and anonymous objects (which we’ll discuss later in this chapter), and function calls that return values by copy (which are a type of temporary object). R-value expressions are typically evaluated for their values.

In many cases, l-values will implicitly convert to r-values, which is why an expression like this works fine:

Now consider this snippet:

In this statement, the variable x is being used in two different contexts. On the left side of the assignment operator, x is being used as an l-value (variable with an address). On the right side of the assignment operator, x is being used as an r-value, and will be evaluated to produce a value (in this case, 2). When C++ evaluates the above statement, it evaluates as:

Which makes it obvious that C++ will assign the value 3 back into variable x.

Note that function return values by copy are an r-value:

In the above example, getDog() return an r-value, so it can not be assigned to.

Temporary objects

// move to chapter on temporary and anonymous objects

In certain cases, the compiler will create temporary objects for us. For example:

When the compiler evaluates x+1, the result 3 must be stored somewhere before it is assigned back to x. A temporary object is constructed to store the value until it can be assigned.

The Pet example above is another example -- the return value of getDog() returns a temporary object of type Pet. We must use this return value immediately or it will be discarded.

These expressions that create temporary objects are r-values. For the most part, you don’t need to worry about temporary objects, as they’re created, used, and destroyed implicitly. Later in this chapter, we’ll talk about how you can intentionally create your own temporary objects.


8.5 -- L-value references
Index
8.3 -- Scoped enumerations (enum classes)

Leave a Reply

You can use these HTML tags

<a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code class="" title="" data-url=""> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong> <pre class="" title="" data-url=""> <span class="" title="" data-url="">