Категории значений
выражение (оператор с его операндами, литерал, имя переменной и т. д.) характеризуется двумя независимыми свойствами: типом и категорией значения. Каждое выражение имеет некоторый тип, не являющийся ссылочным, и каждое выражение принадлежит ровно к одной из трёх основных категорий значений: prvalue, xvalue и lvalue.
- glvalue («обобщённое» lvalue) — это выражение, вычисление которого определяет идентичность объекта или функции;
- prvalue («чистое» rvalue) — это выражение, вычисление которого
- вычисляет значение операнда встроенного оператора (такое prvalue не имеет объекта-результата) или
- инициализирует объект (говорят, что такое 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;
| Дополнительное содержимое |
|---|
| Исторически названо так потому, что rvalue могли располагаться в правой части выражения присваивания. В общем случае это не всегда так: #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:
- имя переменной, функции, объекта-параметра шаблона(начиная с C++20) или члена данных независимо от типа, например
std::cinилиstd::endl. Даже если тип переменной — rvalue-ссылка, выражение, состоящее из её имени, является выражением lvalue (но см. раздел Выражения, пригодные для перемещения);
| Дополнительное содержимое |
|---|
void foo() {}
void baz()
{
// `foo` is lvalue
// address may be taken by built-in address-of operator
void (*p)() = &foo;
}
struct foo {};
template <foo a>
void baz()
{
const foo* obj = &a; // `a` is an lvalue, template parameter object
}
|
- вызов функции или выражение перегруженного оператора, тип результата которого является 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()` is lvalue, function call whose return type is lvalue reference
}
|
-
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; // template parameter is lvalue
}
int a{3}; // static variable, fixed address is known at compile-time
void foo()
{
set<a>();
}
| (начиная с 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; - элемент перечисления;
- нетиповой параметр шаблона скалярного типа;
template <int v>
void foo()
{
// not an lvalue, `v` is a template parameter of scalar type int
const int* a = &v; // ill-formed
v = 3; // ill-formed: lvalue required as left operand of assignment
}
| (начиная с C++11) |
| (начиная с C++20) |
Свойства:
- То же, что и у rvalue (см. ниже).
- prvalue не может быть полиморфным: динамический тип обозначаемого им объекта всегда совпадает с типом выражения.
- prvalue неклассового типа, не являющегося массивом, не может иметь cv-квалификаторы. (Примечание: вызов функции или выражение приведения может дать 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— тернарное условное выражение для некоторых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 {};
// Example from C++23 standard: 7.2.1 Value category [basic.lval]
struct A
{
int m;
};
A&& operator+(A, A);
A&& f();
int main()
{
A a;
A&& ar = static_cast<A&&>(a);
// Function call with return type rvalue reference is xvalue
static_assert(is_xvalue<decltype( (f()) )>::value);
// Member of object expression, object is xvalue, `m` is a non-static data member
static_assert(is_xvalue<decltype( (f().m) )>::value);
// A cast expression to rvalue reference
static_assert(is_xvalue<decltype( (static_cast<A&&>(a)) )>::value);
// Operator expression, whose return type is rvalue reference to object
static_assert(is_xvalue<decltype( (a + a) )>::value);
// Expression `ar` is lvalue, `&ar` is valid
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, продлевается до окончания области видимости ссылки.
| (начиная с 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-ссылку нельзя связать с ним. Константную 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, в C++ стали выражениями lvalue.
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, 6.3.2.1/10.
- «Новая» терминология категорий значений, Бьярне Страуструп, 2010.
- Константные prvalue (разрешённые только для типов классов) и константные xvalue не связываются с перегрузками
T&&, но связываются с перегрузками const T&&, которые стандарт также классифицирует как «перемещающий конструктор» и «оператор перемещающего присваивания», удовлетворяя определению «можно переместить из» для целей этой классификации. Однако такие перегрузки не могут изменять свои аргументы и на практике не используются; при их отсутствии константные prvalue и константные xvalue связываются с перегрузками 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 Lvalue и rvalue [basic.lval]
- Стандарт C++14 (ISO/IEC 14882:2014):
- 3.10 Lvalue и rvalue [basic.lval]
- Стандарт C++11 (ISO/IEC 14882:2011):
- 3.10 Lvalue и rvalue [basic.lval]
- Стандарт C++98 (ISO/IEC 14882:1998):
- 3.10 Lvalue и rvalue [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 |
© cppreference.com
Licensed under the Creative Commons Attribution-ShareAlike Unported License v3.0.
https://en.cppreference.com/cpp/language/value_category