Value categories
Каждое C++ выражение (оператор с операндами, литерал, имя переменной и т.д.) характеризуется двумя независимыми свойствами: типом и категорией значения . Каждое выражение имеет некоторый нессылочный тип, и каждое выражение принадлежит ровно одной из трёх основных категорий значений: prvalue , xvalue и lvalue .
- glvalue («обобщённое» lvalue) — это выражение, вычисление которого определяет идентичность объекта или функции;
- prvalue («чистое» rvalue) — это выражение, вычисление которого
-
- вычисляет значение операнда встроенного оператора (такой prvalue не имеет результирующего объекта ), или
- инициализирует объект (такой prvalue называется имеющим результирующий объект ).
-
Результирующим объектом может быть переменная, объект созданный
new-expression
, временный объект созданный
temporary materialization
, или их член. Заметим, что не-
void
discarded
выражения имеют результирующий объект (материализованный временный объект). Также каждый класс и массив prvalue имеет результирующий объект, кроме случая когда он является операндом
decltype;
- xvalue (значение «истекающего срока») — это glvalue, обозначающее объект, ресурсы которого могут быть повторно использованы;
- lvalue — это glvalue, которое не является xvalue;
| Расширенное содержимое |
|---|
|
Исторически называются так, потому что lvalue могли находиться в левой части выражения присваивания. В общем случае это не всегда верно: void foo(); void baz() { int a; // Expression `a` is lvalue a = 4; // OK, could appear on the left-hand side of an assignment expression int &b{a}; // Expression `b` is lvalue b = 5; // OK, could appear on the left-hand side of an assignment expression const int &c{a}; // Expression `c` is lvalue c = 6; // ill-formed, assignment of read-only reference // Expression `foo` is lvalue // address may be taken by built-in address-of operator void (*p)() = &foo; foo = baz; // ill-formed, assignment of function } |
- rvalue является prvalue или xvalue;
| Расширенное содержимое |
|---|
|
Исторически называются так, потому что rvalues могли появляться в правой части выражения присваивания. В общем случае это не всегда так:
Запустить этот код
#include <iostream> struct S { S() : m{42} {} S(int a) : m{a} {} int m; }; int main() { S s; // Expression `S{}` is prvalue // May appear on the right-hand side of an assignment expression s = S{}; std::cout << s.m << '\n'; // Expression `S{}` is prvalue // Can be used on the left-hand side too std::cout << (S{} = S{7}).m << '\n'; } Вывод: 42 7 |
Примечание: эта таксономия претерпела значительные изменения в предыдущих редакциях стандарта C++, подробности смотрите в разделе История ниже.
| Расширенное содержимое |
|---|
|
Несмотря на их названия, эти термины классифицируют выражения, а не значения.
Запустить этот код
#include <type_traits> #include <utility> template <class T> struct is_prvalue : std::true_type {}; template <class T> struct is_prvalue<T&> : std::false_type {}; template <class T> struct is_prvalue<T&&> : std::false_type {}; template <class T> struct is_lvalue : std::false_type {}; template <class T> struct is_lvalue<T&> : std::true_type {}; template <class T> struct is_lvalue<T&&> : std::false_type {}; template <class T> struct is_xvalue : std::false_type {}; template <class T> struct is_xvalue<T&> : std::false_type {}; template <class T> struct is_xvalue<T&&> : std::true_type {}; int main() { int a{42}; int& b{a}; int&& r{std::move(a)}; // Expression `42` is prvalue static_assert(is_prvalue<decltype((42))>::value); // Expression `a` is lvalue static_assert(is_lvalue<decltype((a))>::value); // Expression `b` is lvalue static_assert(is_lvalue<decltype((b))>::value); // Expression `std::move(a)` is xvalue static_assert(is_xvalue<decltype((std::move(a)))>::value); // Type of variable `r` is rvalue reference static_assert(std::is_rvalue_reference<decltype(r)>::value); // Type of variable `b` is lvalue reference static_assert(std::is_lvalue_reference<decltype(b)>::value); // Expression `r` is lvalue static_assert(is_lvalue<decltype((r))>::value); } |
Содержание |
Основные категории
lvalue
Следующие выражения являются lvalue-выражениями :
- имя переменной, функции , template parameter object (since C++20) , или члена данных, независимо от типа, такие как std:: cin или std:: hex . Даже если тип переменной является rvalue-ссылкой, выражение, состоящее из её имени, является lvalue-выражением (но см. Move-eligible expressions );
| Расширенное содержимое |
|---|
void foo() {} void baz() { // `foo` является lvalue // адрес может быть получен с помощью встроенного оператора взятия адреса void (*p)() = &foo; } struct foo {}; template <foo a> void baz() { const foo* obj = &a; // `a` является lvalue, объект параметра шаблона } |
- вызов функции или выражение перегруженного оператора, возвращаемое значение которого является lvalue-ссылкой, например std:: getline ( std:: cin , str ) , std:: cout << 1 , str1 = str2 , или ++ it ;
| Расширенное содержимое |
|---|
int& a_ref() { static int a{3}; return a; } void foo() { a_ref() = 5; // `a_ref()` является lvalue, вызов функции, возвращающий lvalue-ссылку } |
- a = b , a + = b , a % = b , и все другие встроенные выражения присваивания и составного присваивания ;
- ++ a и -- a , встроенные выражения префиксного инкремента и декремента ;
- * p , встроенное выражение косвенного обращения ;
- a [ n ] и p [ n ] , встроенные выражения индексации , где один операнд в a [ n ] является lvalue массива (начиная с C++11) ;
-
a.
m
,
выражение доступа к члену объекта
, за исключением случаев, когда
mявляется членом-перечислением или нестатической функцией-членом, или когда a является rvalue иmявляется нестатическим членом данных объектного типа;
| Расширенное содержимое |
|---|
struct foo { enum bar { m // member enumerator }; }; void baz() { foo a; a.m = 42; // ill-formed, lvalue required as left operand of assignment } struct foo { void m() {} // non-static member function }; void baz() { foo a; // `a.m` is a prvalue, hence the address cannot be taken by built-in // address-of operator void (foo::*p1)() = &a.m; // ill-formed void (foo::*p2)() = &foo::m; // OK: pointer to member function } struct foo { static void m() {} // static member function }; void baz() { foo a; void (*p1)() = &a.m; // `a.m` is an lvalue void (*p2)() = &foo::m; // the same } |
-
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-ссылки;
template <int& v> void set() { v = 5; // параметр шаблона является lvalue } int a{3}; // статическая переменная, фиксированный адрес известен во время компиляции void foo() { set<a>(); }
|
(начиная с C++11) |
Свойства:
- То же, что и glvalue (ниже).
- Адрес lvalue может быть получен с помощью встроенного оператора взятия адреса: & ++ i [1] и & std:: hex являются допустимыми выражениями.
- Изменяемое 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; - перечислитель ;
- константный шаблонный параметр скалярного типа;
template <int v> void foo() { // не lvalue, `v` является параметром шаблона скалярного типа int const int* a = &v; // некорректно v = 3; // некорректно: требуется lvalue в левой части присваивания }
|
(since C++11) |
|
(since C++20) |
Свойства:
- То же, что и rvalue (ниже).
- prvalue не может быть полиморфным : динамический тип объекта, который он обозначает, всегда соответствует типу выражения.
- prvalue неклассового типа и не типа массива не может быть cv-квалифицирован , за исключением случаев, когда он материализуется для привязки к ссылке на cv-квалифицированный тип (начиная с C++17) . (Примечание: вызов функции или приведение типа может приводить к prvalue cv-квалифицированного неклассового типа, но квалификатор обычно немедленно удаляется.)
-
prvalue не может иметь
неполный тип
(за исключением типа
void
, см. ниже, или при использовании в спецификаторе
decltype). - prvalue не может иметь тип абстрактного класса или массив таких типов.
xvalue
Следующие выражения являются xvalue выражениями :
-
a.
m
, выражение
доступа к члену объекта
, где
a
является rvalue, а
m— нестатическим членом-данным объектного типа; -
a.
*
mp
, выражение
доступа к члену по указателю
, где
a
является rvalue, а
mp— указателем на член-данные; - a, b , встроенное выражение запятой , где b является xvalue;
- a ? b : c , выражение тернарного условия для определённых b и c (см. определение для деталей);
|
(начиная с C++11) |
|
(начиная с C++17) |
|
(начиная с C++23) |
Свойства:
- То же, что и rvalue (ниже).
- То же, что и glvalue (ниже).
В частности, как и все rvalue, xvalue связываются с rvalue-ссылками, и как и все glvalue, xvalue могут быть полиморфными , а неклассовые xvalue могут иметь cv-квалификаторы .
| Расширенное содержимое |
|---|
|
Запустить этот код
#include <type_traits> template <class T> struct is_prvalue : std::true_type {}; template <class T> struct is_prvalue<T&> : std::false_type {}; template <class T> struct is_prvalue<T&&> : std::false_type {}; template <class T> struct is_lvalue : std::false_type {}; template <class T> struct is_lvalue<T&> : std::true_type {}; template <class T> struct is_lvalue<T&&> : std::false_type {}; template <class T> struct is_xvalue : std::false_type {}; template <class T> struct is_xvalue<T&> : std::false_type {}; template <class T> struct is_xvalue<T&&> : std::true_type {}; // Пример из стандарта C++23: 7.2.1 Категория значения [basic.lval] struct A { int m; }; A&& operator+(A, A); A&& f(); int main() { A a; A&& ar = static_cast<A&&>(a); // Вызов функции с возвращаемым типом rvalue reference является xvalue static_assert(is_xvalue<decltype( (f()) )>::value); // Член выражения объекта, объект является xvalue, `m` - нестатический член данных static_assert(is_xvalue<decltype( (f().m) )>::value); // Выражение приведения к rvalue reference static_assert(is_xvalue<decltype( (static_cast<A&&>(a)) )>::value); // Выражение оператора, возвращаемый тип которого является rvalue reference к объекту static_assert(is_xvalue<decltype( (a + a) )>::value); // Выражение `ar` является lvalue, `&ar` допустимо static_assert(is_lvalue<decltype( (ar) )>::value); [[maybe_unused]] A* ap = &ar; } |
Смешанные категории
glvalue
Выражение glvalue является либо lvalue, либо xvalue.
Свойства:
- glvalue может быть неявно преобразовано в prvalue с помощью lvalue-to-rvalue, array-to-pointer или function-to-pointer неявного преобразования .
- glvalue может быть полиморфным : динамический тип объекта, который он идентифицирует, не обязательно совпадает со статическим типом выражения.
- glvalue может иметь неполный тип , где это разрешено выражением.
rvalue
Выражение rvalue является либо prvalue, либо xvalue.
Свойства:
- Адрес rvalue не может быть взят встроенным оператором взятия адреса: & int ( ) , & i ++ [3] , & 42 , и & std :: move ( x ) являются недопустимыми.
- Rvalue не может использоваться в качестве левого операнда встроенных операторов присваивания или составных операторов присваивания.
- Rvalue может использоваться для инициализации константной lvalue-ссылки , в этом случае время жизни временного объекта, идентифицируемого rvalue, продлевается до конца области видимости ссылки.
|
(since 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 не имеют объекта результата . |
(since C++17) |
Битовые поля
Выражение, которое обозначает битовое поле (например, a. m , где a является lvalue типа struct A { int m : 3 ; } ), является glvalue-выражением: оно может использоваться как левый операнд оператора присваивания, но его адрес не может быть взят, и неконстантная lvalue-ссылка не может быть связана с ним. Константная lvalue-ссылка или rvalue-ссылка может быть инициализирована из glvalue битового поля, но будет создана временная копия битового поля: она не свяжется с битовым полем напрямую.
Выражения, пригодные для перемещенияХотя выражение, состоящее из имени любой переменной, является lvalue-выражением, такое выражение может быть пригодным для перемещения, если оно появляется в качестве операнда Если выражение пригодно для перемещения, оно рассматривается либо как rvalue, либо как lvalue (до C++23) как rvalue (начиная с C++23) для целей разрешения перегрузки (таким образом, может быть выбран move constructor ). Подробности см. в разделе Автоматическое перемещение из локальных переменных и параметров . |
(начиная с C++11) |
История
CPL
Язык программирования CPL первым ввёл категории значений для выражений: все выражения CPL могут вычисляться в «правом режиме», но только определённые виды выражений имеют смысл в «левом режиме». При вычислении в правом режиме выражение рассматривается как правило для вычисления значения (правостороннее значение, или rvalue ). При вычислении в левом режиме выражение фактически даёт адрес (левостороннее значение, или lvalue ). «Левый» и «правый» здесь означали «слева от присваивания» и «справа от присваивания».
C
Язык программирования C следовал аналогичной таксономии, за исключением того, что роль присваивания больше не была значимой: выражения в C разделяются на "lvalue expressions" и другие (функции и необъектные значения), где "lvalue" означает выражение, идентифицирующее объект, "locator value" [4] .
C++98
C++ до 2011 года следовал модели C, но восстановил название "rvalue" для не-lvalue выражений, сделал функции lvalues и добавил правило, что ссылки могут связываться с lvalues, но только ссылки на const могут связываться с rvalues. Несколько не-lvalue выражений C стали lvalue выражениями в C++.
C++11
С введением семантики перемещения в C++11 категории значений были переопределены для характеристики двух независимых свойств выражений [5] :
- имеет идентичность : можно определить, ссылается ли выражение на ту же сущность, что и другое выражение, например, сравнивая адреса объектов или функций, которые они идентифицируют (полученные напрямую или косвенно);
- может быть перемещено : move constructor , move assignment operator , или другая перегрузка функции, реализующая семантику перемещения, может быть связана с выражением.
В C++11 выражения, которые:
- имеют идентичность и не могут быть перемещены, называются lvalue выражениями;
- имеют идентичность и могут быть перемещены, называются xvalue выражениями;
- не имеют идентичности и могут быть перемещены, называются prvalue ("чистые rvalue") выражениями;
- не имеют идентичности и не могут быть перемещены, не используются [6] .
Выражения, которые имеют идентичность, называются "glvalue выражениями" (glvalue означает "обобщённое lvalue"). Как lvalues, так и xvalues являются glvalue выражениями.
Выражения, которые могут быть перемещены, называются "rvalue выражения". Как prvalues, так и xvalues являются rvalue выражениями.
C++17
В C++17 copy elision стал обязательным в некоторых ситуациях, что потребовало разделения prvalue-выражений от временных объектов, инициализируемых ими, в результате чего появилась система, которую мы имеем сегодня. Обратите внимание, что в отличие от схемы C++11, из prvalues больше не производится перемещение.
Сноски
- ↑ Предполагая, что i имеет встроенный тип или оператор пре-инкремента перегружен для возврата по lvalue-ссылке.
- ↑ 2.0 2.1 2.2 2.3 Специальная категория rvalue, см. pending member function call .
- ↑ Предполагая, что i имеет встроенный тип или оператор пост-инкремента не перегружен для возврата по lvalue-ссылке.
- ↑ "Разногласия в сообществе C касались значения lvalue: одна группа считала lvalue любым локатором объекта, другая группа полагала, что lvalue имеет смысл только слева от оператора присваивания. Комитет C89 принял определение lvalue как локатора объекта." -- ANSI C Rationale, 6.3.2.1/10.
- ↑ "Новая" терминология значений от Бьярна Страуструпа, 2010.
-
↑
const prvalues (разрешены только для классовых типов) и const xvalues не связываются с перегрузками
T&&, но связываются с перегрузками const T && , которые также классифицируются стандартом как "move constructor" и "move assignment operator", удовлетворяя определению "может быть перемещено" для целей данной классификации. Однако такие перегрузки не могут модифицировать свои аргументы и не используются на практике; в их отсутствие const prvalues и const xvalues связываются с перегрузками const T & .
Ссылки
- Стандарт C++23 (ISO/IEC 14882:2024):
-
- 7.2.1 Категория значения [basic.lval]
- Стандарт C++20 (ISO/IEC 14882:2020):
-
- 7.2.1 Категория значения [basic.lval]
- Стандарт C++17 (ISO/IEC 14882:2017):
-
- 6.10 Lvalues и rvalues [basic.lval]
- Стандарт C++14 (ISO/IEC 14882:2014):
-
- 3.10 Lvalues и rvalues [basic.lval]
- Стандарт C++11 (ISO/IEC 14882:2011):
-
- 3.10 Lvalues и rvalues [basic.lval]
- Стандарт C++98 (ISO/IEC 14882:1998):
-
- 3.10 Lvalues и rvalues [basic.lval]
Отчеты о дефектах
Следующие отчеты об изменениях в поведении, являющиеся дефектными, были применены ретроактивно к ранее опубликованным стандартам C++.
| DR | Применяется к | Поведение в опубликованной версии | Корректное поведение |
|---|---|---|---|
| CWG 616 | C++11 |
доступ к члену и доступ к члену через
указатель на член rvalue давал prvalue |
переклассифицировано как xvalue |
| CWG 1059 | C++11 | массивы prvalue не могли быть cv-квалифицированы | разрешено |
| CWG 1213 | C++11 | индексация массива rvalue давала lvalue | переклассифицировано как xvalue |
Смотрите также
|
Документация C
для
категорий значений
|
Внешние ссылки
| 1. | Категории значений C++ и decltype: разоблачение мифов — Дэвид Мазьерс, 2021 | |
| 2. |
Эмпирическое определение категории значения выражения
— StackOverflow
|