Spec-Zone.ru › C++

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

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

// 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, продлевается до окончания области видимости ссылки.
  • rvalue можно использовать для инициализации rvalue-ссылки; в таком случае время жизни временного объекта, идентифицируемого rvalue, продлевается до окончания области видимости ссылки.
  • При использовании в качестве аргумента функции, если доступны две перегрузки функции — одна с параметром типа rvalue-ссылки, другая с параметром типа константной 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-ссылку нельзя связать с ним. Константную 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, в 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 больше не перемещают.

Примечания

  1. Предполагается, что i имеет встроенный тип или что оператор префиксного инкремента перегружен так, чтобы возвращать lvalue-ссылку.
  2. Особая категория 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++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

Spec-Zone.ru

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