Variables, lvalues, and rvalues
Variables
A variable, sometimes referred to as an object, takes up memory space somewhere in a program.
The following statements define variables:
int miles_to_go;
double milesPerGallon, price;
std::string name;
Vehicle myCar;
Each variable definition comprises a type specifier (int, double, etc.), followed by a comma-separated list of one or more names (miles_to_go, milesPerGallon, etc.), followed by a semicolon (;) that terminates the list.
Initialization
A definition may provide an initialization for the variable. Here are the various ways to initialize a variable:
Direct Initialization
Direct initialization uses parentheses, (). It was the standard way to initialize variables before C++11.
int value(42); // direct-initialization
Copy Initialization
Copy initialization uses an equal sign.
int value = 42; // copy-initialization
One problem with the copy-initialization option is that it confuses programmers because it looks like an assignment, because it uses the = operator. It is important for C++ programmers not to confuse initialization with assignment. As we will see (in particular when we learn about classes), they are not the same.
List Initialization
List initialization uses braces, {}. It was introduced in C++11, to allow for more consistent and safe initialization.
int value{42}; // list-initialization
This was called aggregate-initialization before C++11. Since C++11, it is now called list-initialization.
List initialization is now the preferred way to initialize a variable, because:
- Improved type safety – generally prevents narrowing conversions, which can lead to unexpected behavior if a value is assigned to a variable of a smaller type.
- Readability – this syntax is often considered more intuitive and easier to read, especially when initializing with multiple values.
- Consistency – this syntax works in most cases, regardless of variable type.
If no initializer is specified for an object, the object is default-initialized. If no initializer is specified for a reference, the program is ill-formed.
There are some cases where a variable that has no initializer is not initialized at all. This means that the program behavior may be undefined — a common cause of bugs.
Examples:
#include <string>
std::string s1; // default-initialization
std::string s2(); // NOT an initialization!
// actually declares a function “s2”
// with no parameter and returns std::string
std::string s3 = "hello"; // copy-initialization
std::string s4("hello"); // direct-initialization
std::string s5{'a'}; // list-initialization (since C++11)
char a[3] = {'a', 'b'}; // aggregate initialization
// (part of list initialization since C++11)
char& c = a[0]; // reference initializationAs you can see, using parentheses is potentially confusing, and using copy-initialization is often confused with assignment.
lvalues and rvalues
There are two kinds of expressions in C++:
- lvalue – An expression that may appear as either the left-hand or right-hand side of an assignment.
- rvalue – An expression that may appear on the right-hand side of an assignment, but not the left-hand side.
Numeric literals are rvalues, and so may not be assigned to
int miles_to_go = 37, units = 502;
double price = 42.0, milesPerGallon = 27;
price * units = total_price; // error: arithmetic expression is not an lvalue
0 = 5; // error: literal constant is not an lvalue
You may find that some errors or warnings issued by the compiler use the terms lvalue and rvalue, so it important to know their meanings.
lvalue References and rvalue References
Prior to C++11, only one type of reference existed in C++, and so it was known as “reference”. For better distinction, the ordinary reference X& is now called an lvalue reference.
When lvalues were originally defined, they were defined as “values that are suitable to be on the left-hand side of an assignment expression”. However once the const keyword was added to the C++, lvalues were split into —
- modifiable lvalues
- non-modifiable lvalues that are const
Lvalue references
A reference that binds to an lvalue, and is marked with a single ampersand (&). It acts as an alias for the bound object.
=========================================================
int x{};
const int y = 0; // or const int x {0};
=========================================================
// l-value references (Table 1)
int &ref1{ x }; // OK
int &ref2{ y }; // Error
int &ref3{ 5 }; // Error
=========================================================
// lvalue references to const (Table 2)
const int &ref4{ x }; // OK
const int &ref5{ y }; // OK but can't modify
const int &ref6{ 5 }; // OK
int &ref7{ 5 }; // Error
=========================================================
Table 1
Type of Values |
lvalue ref can be initialized |
lvalue ref can modify |
|---|---|---|
Modifiable lvalues |
Yes |
Yes |
Non-modifiable lvalues |
No |
No |
rvalues |
No |
No |
Table 2
Type of Values |
lvalue ref to const can be initialized |
lvalue ref to const can modify |
|---|---|---|
Modifiable lvalues |
Yes |
No |
Non-modifiable lvalues |
Yes |
No |
rvalues |
Yes |
No |
Rvalue References
A reference that binds to an rvalue, and is marked with two ampersands (&&). It extends the life of the rvalue temporary objects.
=========================================================
int x{};
const int y = 0; // or const int x {0};
=========================================================
// r-value references (Table 3)
int &&ref7{ x }; // Error
int &&ref8{ y }; // Error
int &&ref9{ 5 }; // OK
ref9 = 6; // OK
=========================================================
// r-value references to const (Table 4)
const int &&ref10{ x }; // Error
const int &&ref11{ y }; // Error
const int &&ref12{ 5 }; // OK but can't modify
=========================================================
Table 3
Type of Values |
rvalue ref can be initialized |
rvalue ref can modify |
|---|---|---|
Modifiable lvalues |
No |
No |
Non-modifiable lvalues |
No |
No |
rvalues |
Yes |
Yes |
Table 4
Type of Values |
rvalue ref to const can be initialized |
rvalue ref to const can modify |
|---|---|---|
Modifiable lvalues |
No |
No |
Non-modifiable lvalues |
No |
No |
rvalues |
Yes |
No |