#094 – Introduction to value categories

An expression is a sequence of literals, variables, operators, and function calls that can be evaluated to produce a result. Expressions may represent values, objects, or functions.

Expressions are not statements

An expression is a block of code that can be evaluated.

Examples:

42
speed
speed + 10
readTemperature()

statement is a complete instruction in the program:

int speed { 80 };
speed = speed + 10;
std::cout << speed << '\\n';

The distinction matters because value categories apply to expressions, not whole statements.

In this statement:

speed = speed + 10;

there are several expressions:

speed
speed + 10
10

Each has a type and a value category.

The properties of an expression

All expressions in C++ have 2 properties:

  1. Value
  2. Type

Expression property 1: type

The type of an expression is the type the expression evaluates to.

Example:

#include <iostream>

int main(){
    auto revolutions { 1200 / 4 };
    auto voltage { 3.3 + 1.2 };

    std::cout << revolutions << '\\n';
    std::cout << voltage << '\\n';

    return 0;
}

// Output:
// 300
// 4.5

The expression:

1200 / 4

has type int, because both operands are integers and the result is an integer.

The expression:

3.3 + 1.2

has type double, because the literals are floating-point values.

Expression property 2: value category

This expression is valid:

speed = 100;

but this is not:

100 = speed;

Obviously, we cannot perform 100 = speed but how does the compiler know that?

The answer lies in the second property of an expression. It’s value category.

A value category tells the compiler whether an expression refers to an object with identity, or whether it is a temporary result.

This explains why speed = 100 is valid.

The left side of assignment must be a modifiable lvalue. The expression speed refers to a real object that can be modified, an lvalue.

Similarly, the compiler rejects 100 = speed because the left operand is not something that can be assigned to. The literal 100 is a rvalue.

Lvalue expressions

An lvalue is short for left-value or locate value. It’s an expression that evaluates to a function or an identifiable object.

A simple variable name is an lvalue:

#include <iostream>

int main(){
    int batteryPercent { 75 };

    batteryPercent = 80;

    std::cout << batteryPercent << '\\n';

    return 0;
}

// Output:
// 80

The expression batteryPercent is an lvalue because it names a specific object in memory.

It can appear on the left side of assignment because it is modifiable:

batteryPercent = 80;

Not every lvalue is modifiable, though. A const variable is immutable.

Rvalue expressions

An rvalue is an expression that produces a value but does not refer to a persistent object that can be assigned to directly.

Literals are rvalues:

42
3.14
true

Temporary results are also rvalues:

batteryPercent + 5
readSensor()

when the function returns by value.

Example:

#include <iostream>

int readSensor(){
    return 27;
}

int main(){
    int temperature { readSensor() };

    std::cout << temperature << '\\n';

    return 0;
}

// Output:
// 27

The expression readSensor() produces the value 27, but it does not name an object that you can assign to.

This is invalid:

readSensor() = 30; // error

The function call returns a temporary value. You can use that value, but you cannot assign to it.

The C++11 categories

Before C++11, value categories were usually taught as:

  • lvalue
  • rvalue

C++11 refined the model to support move semantics. The modern categories are:

  • glvalue
  • prvalue
  • xvalue
  • lvalue
  • rvalue

The more advanced categories become important when studying move semantics, rvalue references, and perfect forwarding.

Summary

Every C++ expression has a type and a value category. The type tells the compiler what kind of value the expression produces. The value category tells the compiler whether the expression identifies an object or is just a temporary result.

An lvalue refers to an identifiable object or function.

An rvalue is a temporary value or result that cannot usually be assigned to directly