Категории значений
Каждая C++ выражение (оператор с его операндами, литерал, имя переменной и т. д.) характеризуется двумя независимыми свойствами: типом и категорией значения. Каждое выражение имеет некоторый тип, не являющийся ссылкой, и каждое выражение принадлежит ровно одной из трёх основных категорий значений: prvalue, xvalue и lvalue.
- glvalue («обобщённое» lvalue) — это выражение, вычисление которого определяет тождество объекта или функции;
- prvalue («чистое» rvalue) — это выражение, вычисление которого
- вычисляет значение операнда встроенного оператора (такое prvalue не имеет результирующего объекта), или
- инициализирует объект (такое prvalue называется имеющим результирующий объект).
- xvalue («исчерпываемое» значение) — это glvalue, обозначающее объект, ресурсы которого могут быть повторно использованы;
- lvalue (так называемое, исторически, так как lvalue могут появляться в левой части выражения присваивания) — это glvalue, которое не является xvalue;
- rvalue (так называемое, исторически, так как rvalues могут появляться в правой части выражения присваивания) — это prvalue или xvalue.
Примечание: эта таксономия претерпела существенные изменения в ходе пересмотра стандарта C++, см. История ниже для получения подробностей.
Основные категории
lvalue
Следующие выражения являются lvalue-выражениями:
- имя переменной, функции, объекта шаблона(с C++20), или члена данных, независимо от типа, например
std::cinилиstd::endl. Даже если тип переменной — ссылка на rvalue, выражение, состоящее из её имени, является lvalue-выражением (но см. Выражения, допускающие перемещение); - вызов функции или выражение перегруженного оператора, тип возвращаемого значения которого — ссылка на lvalue, например
std::getline(std::cin, str),std::cout << 1,str1 = str2, или++it; -
a = b,a += b,a %= b, и все другие встроенные выражения присваивания и составного присваивания; -
++aи--a, встроенные префиксные выражения инкремента и декремента; -
*p, встроенное выражение косвенного обращения; -
a[n]иp[n], встроенные выражения индексации, где один операнд вa[n]является массивом lvalue(с C++11); -
a.m, выражение доступа к члену объекта, за исключением случаев, когдаm— это перечислитель члена или нестатическая функция-член, или когдаa— это rvalue, аm— это нестатический член данных типа объекта; -
p->m, встроенное выражение доступа к члену указателя, за исключением случаев, когдаm— это перечислитель члена или нестатическая функция-член; -
a.*mp, выражение указателя на член объекта, гдеa— это lvalue, аmp— это указатель на данные; -
p->*mp, встроенное выражение указателя на член указателя, гдеmp— это указатель на данные; -
a, b, встроенное выражение запятой, гдеb— это lvalue; -
a ? b : c, условное выражение с тремя операндами для определённыхbиc(например, когда оба являются lvalue одного типа, но см. определение для деталей); - строковый литерал, например
"Hello, world!"; - выражение приведения типов к типу lvalue ссылки, например
static_cast<int&>(x)илиstatic_cast<void(&)(int)>(x); - нетиповой параметр шаблона типа lvalue ссылки;
| (с C++11) |
Свойства:
- Те же, что и у glvalue (ниже).
- Адрес lvalue можно получить с помощью встроенного оператора взятия адреса:
&++i[1] и&std::endl— допустимые выражения. - Изменяемое lvalue может быть использовано в качестве левого операнда встроенных операторов присваивания и составного присваивания.
- Lvalue может быть использовано для инициализации ссылки lvalue; это связывает новое имя с объектом, идентифицируемым выражением.
prvalue
Следующие выражения являются prvalue-выражениями:
- литерал (кроме строкового литерала), например
42,trueилиnullptr; - вызов функции или выражение перегруженного оператора, тип возвращаемого значения которого — не ссылка, например
str.substr(1, 2),str1 + str2, илиit++; -
a++иa--, встроенные постфиксные выражения инкремента и декремента; -
a + b,a % b,a & b,a << b, и все другие встроенные арифметические выражения; -
a && b,a || b,!a, встроенные логические выражения; -
a < b,a == b,a >= b, и все другие встроенные выражения сравнения; -
&a, встроенное выражение взятия адреса; -
a.m, выражение доступа к члену объекта, гдеm— это перечислитель члена или нестатическая функция-член[2]; -
p->m, встроенное выражение доступа к члену указателя, гдеm— это перечислитель члена или нестатическая функция-член[2]; -
a.*mp, выражение указателя на член объекта, гдеmp— это указатель на функцию-член[2]; -
p->*mp, встроенное выражение указателя на член указателя, гдеmp— это указатель на функцию-член[2]; -
a, b, встроенное выражение запятой, гдеb— это prvalue; -
a ? b : c, условное выражение с тремя операндами для определённыхbиc(см. определение для деталей); - выражение приведения типов к типу не являющемуся ссылкой, например
static_cast<double>(x),std::string{}, или(int)42; - указатель
this; - перечислитель;
- нетиповой параметр шаблона скалярного типа;
| (с C++11) |
| (с C++20) |
Свойства:
- Такие же, как у rvalue (ниже).
- prvalue не может быть полиморфным: динамический тип объекта, который он обозначает, всегда является типом выражения.
- prvalue не являющийся классом или массивом не может быть cv-квалифицированным, если только он не материализуется, чтобы быть связанным с ссылкой на cv-квалифицированный тип(с C++17). (Примечание: выражение вызова функции или приведения типа может привести к prvalue не являющемуся классом cv-квалифицированного типа, но cv-квалификатор обычно сразу же удаляется.)
- prvalue не может иметь неполный тип (кроме типа void, см. ниже, или при использовании в
decltypeспецификаторе). - prvalue не может иметь тип абстрактного класса или массив из него.
xvalue
Следующие выражения являются xvalue выражениями:
-
a.m, выражение члена объекта, гдеa— rvalue, аm— нестатическое поле типа объекта; -
a.*mp, выражение указателя на член объекта, гдеa— rvalue, аmp— указатель на поле; -
a, b, встроенное выражение запятой, гдеb— xvalue; -
a ? b : c, выражение условного оператора ternary для определенныхbиc(подробности см. в определении);
| (с C++11) |
| (с C++17) |
| (с C++23) |
Свойства:
- Такие же, как у rvalue (ниже).
- Такие же, как у glvalue (ниже).
В частности, как и все rvalue, xvalue связываются со ссылками на rvalue, и, как все glvalue, xvalue могут быть полиморфными, а xvalue, не являющиеся классами, могут быть cv-квалифицированы.
Смешанные категории
glvalue
Выражение glvalue — это либо lvalue, либо xvalue.
Свойства:
- Выражение glvalue может быть неявно преобразовано в prvalue с помощью преобразований lvalue-в-rvalue, массива-в-указатель или функции-в-указатель неявного преобразования.
- glvalue может быть полиморфным: динамический тип объекта, который он идентифицирует, необязательно совпадает со статическим типом выражения.
- glvalue может иметь неполный тип, если это разрешено выражением.
rvalue
Выражение rvalue — это либо prvalue, либо xvalue.
Свойства:
- Адрес rvalue не может быть получен с помощью встроенного оператора взятия адреса:
&int(),&i++[3],&42, и&std::move(x)— недопустимы. - Rvalue не может использоваться в качестве левого операнда встроенных операторов присваивания или сложного присваивания.
- Rvalue может использоваться для инициализации ссылки на const lvalue, в этом случае срок жизни временного объекта, идентифицируемого rvalue, продлевается до конца области действия ссылки.
| (с C++11) |
Специальные категории
Ожидаемый вызов члена-функции
Выражения a.mf и p->mf, где mf — нестатическая член-функция, и выражения a.*pmf и p->*pmf, где pmf — указатель на член-функцию, классифицируются как prvalue выражения, но они не могут использоваться для инициализации ссылок, в качестве аргументов функций или для каких-либо целей, кроме как в качестве левого операнда оператора вызова функции, например (p->*pmf)(args).
Выражения типа void
Выражения вызова функций, возвращающие void, выражения приведения к типу void и выражения throw классифицируются как prvalue выражения, но они не могут использоваться для инициализации ссылок или в качестве аргументов функций. Они могут использоваться в контекстах игнорирования значения (например, на отдельной строке, как левый операнд оператора запятой и т. д.) и в операторе return в функции, возвращающей void. Кроме того, выражения throw могут использоваться в качестве второго и третьего операндов условного оператора ?:.
| Выражения типа void не имеют объекта результата. | (с C++17) |
Битовые поля
Выражение, обозначающее битовое поле (например, a.m, где a — lvalue типа struct A { int m: 3; }) — выражение glvalue: оно может использоваться в качестве левого операнда оператора присваивания, но его адрес нельзя получить, и ссылка на lvalue не может быть связана с ним. Ссылка на const lvalue или rvalue может быть инициализирована выражением glvalue битового поля, но будет сделана временная копия битового поля: она не будет связана непосредственно с битовым полем.
Выражения, допускающие перемещениеХотя выражение, состоящее из имени переменной, является выражением lvalue, такое выражение может допускать перемещение, если оно появляется как операнд Если выражение допускает перемещение, оно обрабатывается либо как rvalue, либо как lvalue(до C++23)как rvalue(с C++23) для целей разрешения перегрузки (следовательно, может быть выбран конструктор перемещения). Подробности см. в Автоматическое перемещение из локальных переменных и параметров. | (с C++11) |
История
CPL
Язык программирования CPL первым ввёл категории значений для выражений: все выражения CPL можно вычислять в «правой части», но только некоторые виды выражений имеют смысл в «левой части». При вычислении в правой части выражение рассматривается как правило для вычисления значения (правое значение или rvalue). При вычислении в левой части выражение фактически задаёт адрес (левое значение или lvalue). «Левая» и «правая» здесь обозначают «слева от присваивания» и «справа от присваивания».
C
Язык программирования C следовал аналогичной классификации, за исключением того, что роль присваивания больше не была существенной: выражения C классифицируются как «выражения lvalue» и другие (функции и значения, не являющиеся объектами), где «lvalue» означает выражение, идентифицирующее объект, «локатор значения»[4].
C++98
До 2011 года C++ следовал модели C, но восстановил название «rvalue» для выражений, не являющихся lvalue, превратил функции в lvalue и добавил правило, что ссылки могут связываться с lvalue, но только ссылки на const могут связываться с rvalue. Несколько выражений C, не являющихся lvalue, стали выражениями lvalue в C++.
C++11
С введением семантики перемещения в C++11 категории значений были переопределены для характеристики двух независимых свойств выражений[5]:
- имеет идентификатор: можно определить, ссылается ли выражение на тот же объект, что и другое выражение, например, сравнивая адреса объектов или функций, которые они идентифицируют (полученные непосредственно или косвенно);
- может быть перемещён: конструктор перемещения, оператор присваивания перемещением или другой перегруженный функцией, реализующий семантику перемещения, может связаться с выражением.
В C++11 выражения, которые:
- имеют идентификатор и не могут быть перемещены, называются выражениями lvalue;
- имеют идентификатор и могут быть перемещены, называются выражениями xvalue;
- не имеют идентификатора и могут быть перемещены, называются выражениями prvalue («чистое rvalue»);
- не имеют идентификатора и не могут быть перемещены, не используются[6].
Выражения, имеющие идентификатор, называются «выражениями glvalue» (glvalue означает «обобщённое lvalue»). Lvalue и xvalue являются выражениями glvalue.
Выражения, которые могут быть перемещены, называются «выражениями rvalue». Prvalue и xvalue являются выражениями rvalue.
C++17
В C++17 удаление копирования стало обязательным в некоторых ситуациях, и это потребовало разделения выражений prvalue от временных объектов, инициализированных ими, что привело к современной системе. Обратите внимание, что в отличие от схемы C++11, prvalue больше не перемещаются.
Примечания
- Предполагается, что
iимеет встроенный тип или оператор предварительного инкремента перегружен для возврата по ссылке lvalue. - Специальная категория rvalue, см. ожидаемый вызов члена функции.
- Предполагается, что
iимеет встроенный тип или оператор постфиксного инкремента не перегружен для возврата по ссылке lvalue. - «Разногласия в сообществе C касались значения lvalue, одна группа считала lvalue любым локатором объекта, другая группа утверждала, что lvalue имеет смысл слева от оператора присваивания. Комитет C89 принял определение lvalue как локатора объекта» — ANSI C Rationale, 6.3.2.1/10.
- "Новые" термины значений Бьярне Страуструпа, 2010.
- const prvalue (разрешены только для типов классов) и const xvalue не связываются с
T&&перегрузками, но они связываются сconst T&&перегрузками, которые также классифицируются стандартом как «конструктор перемещения» и «оператор присваивания перемещением», удовлетворяя определению «может быть перемещён» для целей этой классификации. Однако такие перегрузки не могут изменять свои аргументы и не используются на практике; в их отсутствие const prvalue и const xvalue связываются сconst T&перегрузками.
Отчёты о дефектах
Следующие отчёты о дефектах, изменяющие поведение, были применены ретроактивно к ранее опубликованным стандартам C++.
| DR | Применимо к | Поведение, как опубликовано | Правильное поведение |
|---|---|---|---|
| CWG 616 | C++11 | доступ к членам и доступ к членам через указатель на член rvalue приводил к prvalue | переклассифицировано как xvalue |
| CWG 1059 | C++11 | массивы prvalue не могли быть cv-квалифицированы | разрешено |
| CWG 1213 | C++11 | индексация массива rvalue приводила к lvalue | переклассифицировано как xvalue |
См. также
| Документация C по категориям значений |
Внешние ссылки
| David Mazières, 2021 - C++ категории значений и decltype, разгадка |
© cppreference.com
Licensed under the Creative Commons Attribution-ShareAlike Unported License v3.0.
https://en.cppreference.com/w/cpp/language/value_category