Чтобы получить представление о понятиях lvalue и rvalue, проще всего рассмотреть, как выглядит некая переменная с точки зрения компилятора, так сказать "изнутри".int i = 10;
С переменной i связаны две вещи: адрес и значение объекта, который она обозначает. Пусть, для определенности, адрес i — 0x1000. Значение, помещенное по этому адресу, как мы видим, — число 10. Незаметно для нас компилятор попеременно использует то одно, то другое из этих двух чисел. Например:int j; j = i;
В этом месте компилятор должен сгенерировать команду (или последовательность команд), которая означает следующее: «извлечь значение переменной i (число 10, записанное по адресу 0x1000) и записать его по адресу переменной j».
Или:++i; «Нарастить то, что находится по адресу переменной i (0x1000) на 1»
.i = 20; «Поместить по адресу 0x1000 значение 20».
В отличие от переменной i, с литералом 20 никакие адреса не связаны, только значение. В частности, выражения вида: ++20; 20 = 10; никакого смысла в С или C++ не имеют. Таким образом, с любым выражением связаны либо адрес и значение, либо только значение.
Для того, чтобы отличать выражения, обозначающие объекты, от выражений, обозначающих только значения, ввели понятия lvalue и rvalue. Изначально слово lvalue использовалось для обозначения выражений, которые могли стоять слева от знака присваивания (left-value); им противопоставлялись выражения, которые могли находиться только справа от знака присваивания (right-value). Развитие языков C и C++ привело к утрате словом lvalue своего первоначального значения. Иногда lvalue трактуют также как locator value.
С каждой функцией компилятор также связывает две вещи: ее адрес и ее тело («значение»). При необходимости выражение, обозначающее функцию, компилятор может привести к указателю на эту функцию. «Значениями» функций в языках C и C++ оперировать нельзя. В спецификации языка C++ термин lvalue относится также и к выражениям, обознающим функции. В стандарте языка C для выражений, обозначающих функции, используется отдельный термин — function designator.
Необходимо подчеркнуть, что lvalue/rvalue является свойством не объектов и/или функций, а выражений. Например:char a [10];
i, ++i, *&i, a[i] — lvalue, однако:10,i+1,i++ — rvalue; modifiable lvalues, non-modifiable lvalues.
Не все выражения, являющиеся lvalue, могут быть использованы для модификации обозначаемых ими объектов. Например, если t определена как "const int& t = i;", выражение t, будучи lvalue, не может быть использовано для модификации объекта, который оно обозначает. Такие выражения называют non-modifiable lvalues; им противопоставляют modifiable lvalues, которые могут быть использованы для модификации обозначаемых ими объектов.
lvalue-to-rvalue conversion
Очевидно, что если мы будем использовать любое из приведенных ранее lvalue-выражений в контексте, требующем значения объекта (например, в правой части операции присваивания), использование компилятором адресов будет ошибкой. Т.е., хотя выражение i является lvalue ("адрес и значение"), компилятор должен использовать соответствующее rvalue ("значение"). В терминах стандарта C++ это называется преобразованием lvalue к rvalue (lvalue-to-rvalue conversion).
Критерии lvalue/rvalue
В качестве критерия того, является ли некоторое выражение e lvalue, часто предлагают использовать возможность помещения этого выражения по левую сторону от знака присваивания, т.е. если выражение "e =..." допустимо, то e — lvalue. Однако этот критерий «не работает». Во-первых он не «пропускает» некоторые lvalues. Например, хотя выражения, состоящие только из имени массива или функции, являются lvalue, они не могут стоять по левую сторону от '='. Во-вторых он может ложно «диагностировать» некоторые rvalues как lvalues. Например, в C++ для rvalues классов можно вызывать функции члены, поэтому некоторые rvalues вполне могут находиться слева от '=':class C { };
int main()
{
C() = C(); // OK
return 0;
}
Данная программа, хотя и не слишком осмысленна, вполне «законна» с точки зрения спецификации языка C++. При этом выражение С(), стоящее слева от '=', по правилам языка C++ является rvalue,
rvalue:
Выражения, обозначающие временные объекты. В частности, результат вызова функций, возвращающих объекты не по ссылке; результат встроенных операций +, -. *, / и т.п.; явное создание временной переменной int() или C(); преобразования не к ссылочным типам и т.д.
Результат встроенной операции взятия адреса (&) — rvalue типа указатель.
Результат встроенных постфиксных операций ++, --.
Литералы за исключением строковых.
Константы перечислений.
lvalue:
Выражения, непосредственно обозначающие объект, non-modifiable в случае const-квалификации. Например, имя переменной, параметра функции и т.п.
Выражения ссылочных типов. non-modifiable в случае const-квалификации. В частности, результат вызова функций, возвращающих объекты по ссылке; выражения, состоящие из имен ссылочных переменных; операции преобразования к ссылочному типу и т.д.
Результат встроенной операции разыменования (*) — lvalue указуемого типа; non-modifiable в случае const-квалификации.
Результат встроенных префиксных операций ++, --.
Имя функции — non-modifiable lvalue; может быть преобразовано к rvalue «указатель на функцию».
Имя массива — non-modifiable lvalue; может быть преобразовано к rvalue «указатель на первый элемент массива».
Строковые литералы — non-modifiable lvalue; может быть преобразовано к rvalue «указатель на char/wchar_t».