Namespaces
Variants

Value categories

From cppreference.net
C++ language
General topics
Flow control
Conditional execution statements
Iteration statements (loops)
Jump statements
Functions
Function declaration
Lambda function expression
inline specifier
Dynamic exception specifications ( until C++17* )
noexcept specifier (C++11)
Exceptions
Namespaces
Types
Specifiers
constexpr (C++11)
consteval (C++20)
constinit (C++20)
Storage duration specifiers
Initialization
Expressions
Value categories
Order of evaluation
Alternative representations
Literals
Boolean - Integer - Floating-point
Character - String - nullptr (C++11)
User-defined (C++11)
Utilities
Attributes (C++11)
Types
typedef declaration
Type alias declaration (C++11)
Casts
Memory allocation
Classes
Class-specific function properties
Special member functions
Templates
Miscellaneous

Каждое 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-ссылку
}
Расширенное содержимое
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
}
template <int& v>
void set()
{
    v = 5; // параметр шаблона является lvalue
}
int a{3}; // статическая переменная, фиксированный адрес известен во время компиляции
void foo()
{
    set<a>();
}
  • вызов функции или выражение перегруженного оператора, возвращаемое значение которого является rvalue-ссылкой на функцию;
  • выражение приведения к типу rvalue-ссылки на функцию, например static_cast < void ( && ) ( int ) > ( x ) .
(начиная с C++11)

Свойства:

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

prvalue

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

template <int v>
void foo()
{
    // не lvalue, `v` является параметром шаблона скалярного типа int
    const int* a = &v; // некорректно
    v = 3; // некорректно: требуется lvalue в левой части присваивания
}
  • лямбда-выражение, lambda expression , например [ ] ( int x ) { return x * x ; } ;
(since C++11)
(since C++20)

Свойства:

  • То же, что и rvalue (ниже).
  • prvalue не может быть полиморфным : динамический тип объекта, который он обозначает, всегда соответствует типу выражения.
  • prvalue неклассового типа и не типа массива не может быть cv-квалифицирован , за исключением случаев, когда он материализуется для привязки к ссылке на cv-квалифицированный тип (начиная с C++17) . (Примечание: вызов функции или приведение типа может приводить к prvalue cv-квалифицированного неклассового типа, но квалификатор обычно немедленно удаляется.)
  • prvalue не может иметь неполный тип (за исключением типа void , см. ниже, или при использовании в спецификаторе decltype ).
  • prvalue не может иметь тип абстрактного класса или массив таких типов.

xvalue

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

  • вызов функции или выражение перегруженного оператора, возвращающий тип которого является 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-квалификаторы .

Расширенное содержимое
#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.

Свойства:

rvalue

Выражение rvalue является либо prvalue, либо xvalue.

Свойства:

  • Адрес rvalue не может быть взят встроенным оператором взятия адреса: & int ( ) , & i ++ [3] , & 42 , и & std :: move ( x ) являются недопустимыми.
  • Rvalue не может использоваться в качестве левого операнда встроенных операторов присваивания или составных операторов присваивания.
  • Rvalue может использоваться для инициализации константной lvalue-ссылки , в этом случае время жизни временного объекта, идентифицируемого rvalue, продлевается до конца области видимости ссылки.
  • Rvalue может быть использовано для инициализации rvalue-ссылки , в этом случае время жизни временного объекта, идентифицируемого rvalue, продлевается до конца области видимости ссылки.
  • При использовании в качестве аргумента функции и когда доступны две перегрузки функции, одна принимающая параметр rvalue-ссылку, а другая принимающая lvalue-ссылку на const параметр, rvalue связывается с перегрузкой rvalue-ссылки (таким образом, если доступны и конструктор копирования, и конструктор перемещения, аргумент 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-выражением, такое выражение может быть пригодным для перемещения, если оно появляется в качестве операнда

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

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

Сноски

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