В C++ все переменные являются l-values. l-value (в переводе «л-значение», произносится как «ел-валью») – это значение, которое имеет свой адрес в памяти. Поскольку все переменные имеют адреса, то они все являются l-values (например: переменные a, b, c – все являются l-values). l от слова «left», так как только значения l-values могут находиться в левой стороне в операциях присваивания (в противном случае мы получим ошибку). Например, выражение 9 = 10; вызовет ошибку компилятора, так как 9 не является l-value. Число 9 не имеет своего адреса в памяти и, таким образом, мы ничего не можем ему присвоить (9 равно 9, и ничего здесь не изменить).
Противоположностью l-value является r-value (в переводе «р-значение», произносится как «ер-валью»). r-value – это значение, которое не имеет постоянного адреса в памяти. Примерами могут быть единичные числа (например, 7, которое вычисляется в 7) или выражения (например, x + 3, которое вычисляется в значение "х плюс 3").
Вот несколько примеров операций присваивания с использованием r-values:
int a; // объявляем целочисленную переменную a
a = 5; // 5 вычисляется в 5, которое затем присваивается переменной а
a = 4 + 6; // 4 + 6 вычисляется в 10, которое затем присваивается переменной а
int b; // объявляем целочисленную переменную b
b = a; // a вычисляется в 10 (с предыдущих операций), которое затем присваивается переменной b
b = b; // b вычисляется в 10, которое затем присваивается переменной b (ничего не происходит)
b = b + 2; // b + 2 вычисляется в 12, которое затем присваивается переменной b
Давайте детальнее рассмотрим последнюю операцию присваивания:
b = b + 2;
Здесь переменная b используется в двух различных контекстах. Слева b используется как l-value (переменная с адресом в памяти), справа b используется как r-value и производит отдельное значение (в данном случае 12). При выполнении этого стейтмента, компилятор видит следующее:
b = 10 + 2;
И здесь уже становится понятным, какое значение присваивается переменной b.
Всё, что нужно запомнить – это то, что в левой стороне операции присваивания всегда должно находиться l-value (которое имеет свой адрес в памяти), а в правой стороне операции присваивания – r-value (которое производит какое-то значение).