Пространства имён
Варианты
Действия

Категории значений

Материал из cppreference.com
< cpp‎ | language
 
 
Язык С++
Общие темы
Управление потоком
Операторы условного выполнения
Операторы итерации (циклы)
Операторы перехода
Функции
Объявление функции
Выражение лямбда-функции
Спецификатор inline
Спецификации динамических исключений(до C++17*)
Спецификатор noexcept(C++11)
Исключения
Пространства имён
Типы
Спецификаторы
decltype(C++11)
auto(C++11)
alignas(C++11)
Спецификаторы длительности хранения
Инициализация
Выражения
Категории значений
Порядок оценки
Альтернативные представления
Литералы
Логические - Целочисленные - С плавающей запятой
Символьные - Строковые - nullptr(C++11)
Определяемые пользователем(C++11)
Утилиты
Атрибуты(C++11)
Types
Объявление typedef
Объявление псевдонима типа(C++11)
Casts
Неявные преобразования - Явные преобразования
static_cast - dynamic_cast
const_cast - reinterpret_cast
Выделение памяти
Классы
Свойства функции класса
Специальные функции-элементы
Шаблоны
Разное
 

Каждое выражение С++ (оператор со своими операндами, литерал, имя переменной и т.д.) характеризуется двумя независимыми свойствами: тип и категория значения. Каждое выражение имеет некоторый нессылочный тип, и каждое выражение принадлежит ровно к одной из трех основных категорий значений: 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:

  • вызов функции или выражение перегруженного оператора, возвращаемым типом которого является rvalue ссылка на функцию;
  • выражение приведения к rvalue ссылке на тип функцию, например static_cast<void(&&)(int)>(x).
(начиная с C++11)

Свойства:

  • То же, что и glvalue (смотрите ниже).
  • Адрес lvalue может быть получен встроенным оператором взятия адреса: &++i[1] и &std::endl являются допустимыми выражениями.
  • Изменяемое lvalue может использоваться как левый операнд встроенных операторов присваивания и составного присваивания.
  • lvalue может использоваться для инициализации ссылки на lvalue; это связывает новое имя с объектом, идентифицированным выражением.

[править]prvalue

Следующие выражения являются выражениями prvalue:

(начиная с 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 (подробнее смотрите определение);
  • вызов функции или выражение перегруженного оператора, возвращаемый тип которого является ссылкой rvalue на объект, например std::move(x);
  • a[n], встроенное выражение subscript, где один операнд является массивом rvalue;
  • выражение приведения к ссылке rvalue на тип объекта, например static_cast<char&&>(x);
(начиная с 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, продлевается, пока не закончится область действия ссылки.
  • rvalue может использоваться для инициализации ссылки на rvalue, и в этом случае время жизни объекта, идентифицированного rvalue, продлевается, пока не закончится область видимости ссылки.
  • При использовании в качестве аргумента функции и когда доступны две перегрузки функции, одна из которых принимает ссылку на rvalue в качестве параметра, а другая принимает константную ссылку на lvalue в качестве параметра, rvalue привязывается к перегрузке со ссылкой на rvalue (таким образом , если доступны конструкторы копирования и перемещения, аргумент 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, такое выражение может быть пригодным для перемещения, если оно появляется в качестве операнда

  • оператора return
  • оператора co_return(начиная с C++20)
  • выражения throw(начиная с C++17)

Если выражение допускает перемещение, оно обрабатывается либо как 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 больше не перемещаются из.

[править]Сноски

  1. Предполагая, что i имеет встроенный тип или оператор преинкремента перегружен, чтобы возвращать ссылку на lvalue.
  2. 2,02,12,22,3Специальная категория rvalue, смотрите ожидающий вызов функции-элемента.
  3. Предполагая, что i имеет встроенный тип или оператор постинкремента не является перегруженным, чтобы возвращать ссылку на lvalue.
  4. "Разница во мнениях в сообществе C была сосредоточена вокруг значения lvalue, одна группа считала lvalue любым типом локатора объекта, другая группа считала, что lvalue имеет смысл слева от оператора присваивания. Комитет C89 принял определение lvalue как локатор объекта." -- Обоснование ANSI C, 6.3.2.1/10.
  5. "Новая" Терминология Значений] Бьярна Страуструпа, 2010.
  6. константных 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++
close