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()
A 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:
- Value
- 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