L-Values vs R-Values in C++

L-Values vs R-Values in C++

R-Values were introduced in C++11. Now, in C++, expressions can be categorized as L-Values or R-Values, and understanding the differences between L-Value and R-Value is important when dealing with assignments, function returns, and references. In this tutorial, we will discuss L-Values and R-Values in detail, and this will act as a foundation for move semantics in upcoming tutorials.

What is an L-Value in C++11? #

L-Values (left values) refer to locations in memory that can be identified and accessed. They persist beyond a single expression, meaning you can assign new values to them. In simple terms, an L-Value is something that has a name and an address, like variables.

For example,

int num = 22;

In this expression, the variable num is an l-value. Now, how do we identify what is an l-value in an expression? For this, we have a set of properties, and items that adhere to these properties in an expression can be identified as l-values.

Properties of L-Values: #

  • L-Value has a persistent memory location (i.e., can be referenced repeatedly).
  • L-Value can appear on the left-hand side or right-hand side of an assignment.
  • L-Value can be assigned a new value.

Let’s understand these properties of L-Value with examples,

Example of L-Value: #

// x is an L-value
int x = 10;

In this example, x is an L-value because it refers to a specific memory location that holds an integer. We can also modify L-Values like this:

// Valid, as L-values can be
// assigned new values
x = 20;      

We can assign 20 to x without issue because it is an L-Value and therefore a modifiable object.

Similarly, the variable y is also an L-Value.

// y is an L-value
int y = 20;

// Both 'x' and 'y' are L-values
x = y;        

In the expression x = y, both x and y are L-Values because:

  • The variable x is on the left-hand side of the assignment. Since x represents a specific memory location that can hold data, it qualifies as an L-Value.
  • The variable y is on the right-hand side of the assignment. The value of y is used to assign to x. Here, y is also an L-Value because it refers to a memory location, and we can retrieve its value for assignment.

Now, I hope we understand what exactly L-Values are in expressions. Next, we will discuss R-Values.

What is an R-Value in C++11? #

R-Values are temporary and do not have a persistent memory address. This means you cannot take the address of an R-Value.

For example,

int num = 22;

In this expression, the 22 is an R-Value.

Basically, R-Values (right values) do not persist beyond the expression that uses them. They are typically literals or temporary objects created by expressions, which means they do not have a specific memory address you can refer to later.

We have a set of properties, and items that adhere to these properties in an expression can be identified as R-Values.

Properties of R-Values in C++11 #

  • R-Value does not have a persistent memory location (no address you can take).
  • R-Value can only appear on the right-hand side of an assignment.
  • R-Value cannot be assigned a new value (they are “read-only” in the context of expressions).

Examples of R-Values in C++11 #

Let’s understand R-Values with the help of some examples. In each example, we will discuss one of the properties of R-Values.

Example 1: R-Values are Temporary #

R-Values do not have a persistent memory location (no address you can take)

// Here `10` is R-Value and 'x' is L-Value
int x = 10;       

// OK, 'x' is a L-value and has an address
int* ptr = &x;

// ERROR: Cannot take the address of a R-value (literal)
int* ptr2 = &10; // Error

Here, x in the expression int x = 10; is an L-Value with a memory location, so we can take its address and assign a value to it. However, 10 in the expression int x = 10; is an R-Value (a literal), and trying to take the address of 10 results in a compilation error.

// ERROR: Cannot take the address of a R-value (literal)
int* ptr2 = &10;

Example 2: R-Values on RHS Only #

R-Values can only appear on the right-hand side of an assignment

R-Values can only appear on the right-hand side (RHS) of an assignment, as they represent temporary values or literals. They cannot be on the left-hand side because they don’t represent a modifiable location in memory.

// Here `20` is R-Value and 'x' is L-Value
int x = 20;   

// OK: 10 is an R-value, can appear on the RHS
x = 10;       

// ERROR: R-value '10' cannot be on the LHS
10 = x;       

In the code above:

  • The expression x = 10; is valid because the R-Value 10 appears on the RHS, and its value is assigned to the L-Value x.
  • The expression 10 = x; is invalid because 10 is an R-Value, and R-Values cannot appear on the LHS of an assignment (i.e., we can’t assign a value to a literal).

Example 3: R-Values are “read-only” #

R-Values are temporary and cannot be assigned a new value. They are “read-only” in expressions, meaning you cannot modify them directly. For example,"

// Here `5` is R-Value and 'x' is L-Value
int x = 5;   

// Here `10` is R-Value and 'y' is L-Value
int y = 10;  

// ERROR: The result of 'x + y' is an R-value,
// cannot be assigned a value
x + y = 15;  

In this example:

  • The expression x + y produces an R-Value (a temporary result of the addition), and you cannot assign a new value to the result of an expression because it is read-only.

So, basically:

  • R-Values are temporary values that do not have a persistent memory location (so you can’t take their address).
  • R-Values can only appear on the right-hand side of an assignment (they can’t be assigned to).
  • R-Values cannot be assigned a new value, as they are considered read-only in expressions."

L-Values vs. R-Values - Explained #

The key differences between L-Values and R-Values are:

  • L-Values are memory locations that persist beyond the expression, and you can take their address (i.e., the & operator works).
  • R-Values are temporary values that exist only within the context of an expression and cannot have their address taken.

For example, here we have both L-Values and R-Values:

// 'a' is an L-value, '10' is an R-value
int a = 10;   

// 'b' is an L-value, 'a' is also an L-value here, assigned to 'b'
int b = a;    

In both the expressions above:

  • a and b are L-Values because they both have memory locations that persist.
  • The number 10 is an R-Value because it’s a literal that exists only for the moment of assignment.

Similarly, when we return temporary objects from a function, that is also an R-Value. For example,

std::string getName() 
{
    return "Mark"; // "Mark" is an R-value
}

// 'getName()' returns an R-value (temporary)
// But variable 'name' is an L-Value
std::string name = getName();

In this example, "Mark" is an R-Value because it’s a temporary string literal created when the function getName() is called. The result of getName() is also an R-Value. However, the variable name is an L-Value since it’s a variable that persists in memory.

Summary #

In this tutorial, we learned about L-Values and R-Values:

  • L-Values:

    • Can appear on the left or right side of an assignment.
    • Have a persistent memory address.
    • Can be referenced or modified.
  • R-Values:

    • Can only appear on the right side of an assignment.
    • Are temporary values with no address that can be referenced.
    • Are typically used for move semantics in C++11 and later.

In upcoming articles, we will learn about R-Value references and move semantics with examples, and we will also show how using R-Value references can optimize the performance of applications.

Author: Varun

A Software Developer with 20 Years of Experience in C/C++.