Категории значений
Каждое выражение С++ (оператор со своими операндами, литерал, имя переменной и т.д.) характеризуется двумя независимыми свойствами: тип и категория значения. Каждое выражение имеет некоторый нессылочный тип, и каждое выражение принадлежит ровно к одной из трех основных категорий значений: prvalue, xvalue, lvalue, и трёх смешанных: rvalue , glvalue.
- glvalue (“обобщённое” lvalue) это выражение, оценка которого определяет идентичность объекта или функции;
- prvalue (“pure” rvalue - “чистое” rvalue) это выражение, вычисление которого
- вычисляет значение операнда встроенного оператора (такое prvalue не имеет объекта результата), или
- инициализирует объект (говорят, что такое prvalue имеет объект результата).
- Объект результата может быть переменной, объектом, созданным выражением new, временным обьектом, созданным временной материализацией, или его элементом. Обратите внимание, что не voidотброшенные выражения имеют объект результата (материализованный временный объект). Кроме того, у каждого класса и массива prvalue есть объект результата, кроме случаев, когда он является операндом decltype;
- xvalue (“eXpiring” значение - “истекающее” значение) это glvalue, которое обозначает объект, ресурсы которого могут быть использованы повторно;
- lvalue (называемое так исторически, потому что lvalue может появляться в левой части выражения присваивания) это glvalue, не являющееся xvalue;
- rvalue (называемое так исторически, потому что rvalue может появляться в правой части выражения присваивания) это prvalue или xvalue.
Примечание: представленная систематизация прошла через значительные изменения в прошлых версиях стандарта С++, подробности смотрите в разделе История ниже.
Содержание |
[править]Основные категории
[править]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 одного типа, но подробности смотрите в определении); - строковый литерал, например "Привет, мир!";
- выражение приведения к ссылочному типу lvalue, например static_cast<int&>(x) или static_cast<void(&)(int)>(x);
| (начиная с 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], или гдеa
это rvalue, аm
нестатический элемент данных не ссылочного типа(до C++11); - p->m, встроенное выражение элемент указателя, где
m
элемент перечислителя или нестатическая функция-элемент[2]; - a.*mp, выражение указатель на элемент объекта, где
mp
указатель на функцию-элемент[2], или гдеa
это rvalue, аmp
указатель на элемент данных(до C++11); - p->*mp, встроенное выражение указатель на элемент указатель, где
mp
это указатель на функцию-элемент[2]; - a, b, встроенное выражение запятая, где
b
это rvalue; - a ? b : c, выражение тернарного условия для определённых
b
иc
(для подробностей смотрите определение); - выражение приведения к не ссылочному типу, например static_cast<double>(x), std::string{} или (int)42;
- указатель
this
; - перечислитель;
- параметр шаблона, не являющийся типом, если его тип не является классом или(начиная с C++20) ссылочным типом lvalue;
| (начиная с C++11) |
| (начиная с C++20) |
Свойства:
- То же, что и rvalue (смотрите ниже).
- prvalue не может быть полиморфным: динамический тип обозначаемого объекта всегда является типом выражения.
- Неклассовое значение prvalue, не являющееся массивом, не может быть cv-квалифицированным, если оно не материализовано для того, чтобы быть привязанным к ссылке на cv-квалифицированный тип(начиная с C++17). (Примечание: вызов функции или выражение приведения могут привести к prvalue неклассового cv-квалифицированного типа, но cv-квалификатор обычно сразу удаляется.)
- prvalue не может иметь неполный тип (за исключением типа void, смотрите ниже, или когда используется в спецификаторе
decltype
) - prvalue не может иметь тип абстрактного класса или его массива.
[править]xvalue
Следующие выражения являются выражениями xvalue:
- вызов функции или выражение перегруженного оператора, возвращаемым типом которых является ссылка rvalue на объект, например std::move(x);
- a[n], встроенное выражение индексирования, где один операнд представляет собой массив rvalue;
- a.m, выражение элемента объекта, где
a
это rvalue, аm
нестатический элемент данных не ссылочного типа; - a.*mp, выражение указателя на элемент объекта, где
a
это rvalue, аmp
указатель на элемент данных; - a ? b : c, выражение тернарного условия для определённых
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 может использоваться для инициализации ссылки на константное lvalue, и в этом случае время жизни объекта, идентифицированного rvalue, продлевается, пока не закончится область действия ссылки.
| (начиная с C++11) |
[править]Специальные категории
[править]Ожидающий вызов функции-элемента
Выражения a.mf и p->mf, где mf
это нестатическая функция-элемент, а выражения a.*pmf и p->*pmf, где pmf
это указатель на функцию-элемент, классифицируются как выражения prvalue, но их нельзя использовать для инициализации ссылок, в качестве аргументов функции или вообще для каких-либо целей, кроме как в качестве левого аргумента оператора вызова функции, например (p->*pmf)(аргументы).
[править]Пустые выражения
Выражения вызова функций, возвращающие void, выражения приведения к void и выражения throw классифицируются как выражения prvalue, но их нельзя использовать для инициализации ссылок или в качестве аргументов функции. Их можно использовать в контекстах отброшенных значений (например, в отдельной строке, как левый операнд оператора запятая и т.д.) и в операторе return в функции, возвращающей void. Кроме того, выражения throw могут использоваться как второй и третий операнды условного оператора ?:.
У пустых выражений нет объекта результата. | (начиная с C++17) |
[править]Битовые поля
Выражение, обозначающее битовое поле (например, a.m, где a
это lvalue типа struct A {int m:3;}) является выражением glvalue: его можно использовать как левый операнд оператора присваивания, но его адрес не может быть взят, и неконстантная ссылка на lvalue не может быть привязана к нему. Константная ссылка на 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
C++ до 2011 года следовал модели C, но восстановил имя "rvalue" для выражений, отличных от lvalue, сделал функции значениями lvalue и добавил правило, согласно которому ссылки могут связываться с lvalue, но только константные ссылки могут связываться с 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.
- ↑ 2,02,12,22,3Специальная категория rvalue, смотрите ожидающий вызов функции-элемента.
- ↑Предполагая, что i имеет встроенный тип или оператор постинкремента не является перегруженным, чтобы возвращать ссылку на lvalue.
- ↑"Разница во мнениях в сообществе C была сосредоточена вокруг значения lvalue, одна группа считала lvalue любым типом локатора объекта, другая группа считала, что lvalue имеет смысл слева от оператора присваивания. Комитет C89 принял определение lvalue как локатор объекта." -- Обоснование ANSI C, 6.3.2.1/10.
- ↑"Новая" Терминология Значений] Бьярна Страуструпа, 2010.
- ↑константных prvalue (разрешены только для типов классов) и константных xvalue не привязаных к перегрузкам T&&, но привязаных к перегрузкам const T&&, которые также классифицируются как "конструктор перемещения" и "оператор присваивания перемещением" по стандарту, соответствующему определению "можно переместить из" для целей данной классификации. Однако такие перегрузки не могут изменять свои аргументы и на практике не используются; в их отсутствие константные prvalue и константные xvalue связываются с перегрузками const T&.
[править]Отчёты о дефектах
Следующие изменения поведения были применены с обратной силой к ранее опубликованным стандартам C++:
Номер | Применён | Поведение в стандарте | Корректное поведение |
---|---|---|---|
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 - Демистификация категорий значений и decltype C++ |