Spec-Zone.ru › C++

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

Каждая C++ выражение (оператор с его операндами, литерал, имя переменной и т. д.) характеризуется двумя независимыми свойствами: типом и категорией значения. Каждое выражение имеет некоторый тип, не являющийся ссылкой, и каждое выражение принадлежит ровно одной из трёх основных категорий значений: prvalue, xvalue и lvalue.

  • glvalue («обобщённое» lvalue) — это выражение, вычисление которого определяет тождество объекта или функции;
  • prvalue («чистое» rvalue) — это выражение, вычисление которого
  • вычисляет значение операнда встроенного оператора (такое prvalue не имеет результирующего объекта), или
  • инициализирует объект (такое prvalue называется имеющим результирующий объект).
Результирующий объект может быть переменной, объектом, созданным new-выражением, временным объектом, созданным посредством материализации временных объектов, или его членом. Обратите внимание, что выражения, не являющиеся voidотброшенными, имеют результирующий объект (материализованный временный объект). Также у каждого класса и массива prvalue есть результирующий объект, за исключением случаев, когда он является операндом decltype;
  • 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 ссылки;
  • вызов функции или выражение перегруженного оператора, тип возвращаемого значения которого — ссылка на rvalue на функцию;
  • выражение приведения типов к типу rvalue ссылки на функцию, например 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];
  • 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;
  • перечислитель;
  • нетиповой параметр шаблона скалярного типа;
  • выражение лямбда, например [](int x){ return x * x; };
(с C++11)
  • выражение requires, например requires (T i) { typename T::type; };
  • специализация концепции, например std::equality_comparable<int>.
(с 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 (подробности см. в определении);
  • вызов функции или выражение перегруженного оператора, тип возврата которого — ссылка на rvalue объекта, например std::move(x);
  • a[n], встроенное выражение индексации, где один операнд — 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 может использоваться для инициализации ссылки на const lvalue, в этом случае срок жизни временного объекта, идентифицируемого rvalue, продлевается до конца области действия ссылки.
  • Rvalue может использоваться для инициализации ссылки на rvalue, в этом случае срок жизни временного объекта, идентифицируемого rvalue, продлевается до конца области действия ссылки.
  • При использовании в качестве аргумента функции и при наличии двух перегрузок функции, одна из которых принимает параметр типа ссылки на rvalue, а другая — ссылку на const lvalue, rvalue связывается с перегрузкой, принимающей ссылку на rvalue (следовательно, если доступны как конструктор копирования, так и конструктор перемещения, аргумент 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, такое выражение может допускать перемещение, если оно появляется как операнд

  • оператора 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

До 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 больше не перемещаются.

Примечания

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

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API