Что такое rvalues, lvalues, xvalues, glvalues ​​и prvalues?

В С++ 03 выражение имеет значение rvalue или lvalue.

В С++ 11 выражение может быть:

  • Rvalue
  • именующего
  • xvalue
  • glvalue
  • prvalue

Две категории стали пятью категориями.

  • Что представляют собой эти новые категории выражений?
  • Как эти новые категории относятся к существующим категориям rvalue и lvalue?
  • Являются ли категории rvalue и lvalue в С++ 0x такими же, как в С++ 03?
  • Почему нужны эти новые категории? Являются ли боги WG21 просто путают нас простых смертных?

Ответы

Ответ 1

Я предполагаю, что этот документ может стать не столь коротким введением: n3055

Вся резня началась с семантики движения. Как только у нас есть выражения, которые можно перемещать и не копировать, внезапно легко понять правила, требующие различия между выражениями, которые можно перемещать, и в каком направлении.

Из того, что я думаю, основываясь на проекте, различие значений r/l остается неизменным, только в контексте перемещения вещей становится беспорядочным.

Они нужны? Наверное, нет, если мы хотим потерять новые возможности. Но чтобы обеспечить лучшую оптимизацию, мы должны, вероятно, охватить их.

Цитата n3055:

  • lvalue (так называемый, исторически, потому что lvalues ​​могут появляться на левая часть задания выражение) обозначает функцию или объект. [Пример: если E является выражение типа указателя, затем *E является выражением lvalue, относящимся к объект или функцию, к которой E точки. В качестве другого примера результат вызова функции, чья Тип возврата - это значение lvalue lvalue.]
  • xvalue ( Значение "eXpiring" также относится к объект, обычно ближе к концу (так что его ресурсы могут например, перемещаются). Значение x равно результат определенных видов выражения, содержащие rvalue Рекомендации. [Пример: результат вызова функции, чья Тип возврата - это rvalue reference xvalue.]
  • glvalue ( "обобщенное" значение lvalue) lvalue или xvalue.
  • rvalue (так называемый, исторически, поскольку rvalues ​​могли появляются в правой части выражение присваивания) является значением x, временный объект или его подобъектом или значением, которое не связанный с объектом.
  • prvalue ( "чистое" rvalue) является rvalue это не значение x. [Пример: результат вызова функции, чья тип возврата не является ссылкой prvalue]

Документ, о котором идет речь, является отличной ссылкой на этот вопрос, поскольку он показывает точные изменения в стандарте, которые произошли в результате введения новой номенклатуры.

Ответ 2

Что представляют собой эти новые категории выражений?

FCD (n3092) имеет отличное описание:

- lvalue (так называемый, исторически, поскольку lvalues ​​могут появляться на левая часть задания выражение) обозначает функцию или объект. [Пример: если E является выражение типа указателя, тогда * E - выражение lvalue, относящееся к объекту или функции, к которым E точки. В качестве другого примера, результат вызова функции, возвращение которой type является ссылкой на lvalue именующий. -end пример]

- значение x (an Значение "eXpiring" также относится к объект, обычно ближе к концу (так что его ресурсы могут быть перемещен, например). Значение x является результат определенных видов выражений с использованием ссылок rvalue (8.3.2). [ Пример: результат вызова функция, возвращающий тип которой Ссылка rvalue - это значение x. -конец пример]

- Значение glvalue ( "обобщенное" lvalue) является lvalue или xvalue.

- Rvalue (так называемый, исторически, потому что rvalues ​​могут появиться на правая часть задания выражения) является x значением, временным объект (12.2) или его подобъект или значение, которое не связано с объект.

- PRvalue ( "чистое" значение r) значение r, которое не является значением x. [ Пример: результат вызова функция, тип возврата которой не является ссылка - это prvalue. Значение a буквальный, такой как 12, 7.3e5, или true также prvalue. -end пример]

Каждый выражение принадлежит точно одному из фундаментальные классификации в эта таксономия: lvalue, xvalue или prvalue. Это свойство выражение называется его значением категория. [Примечание: обсуждение каждый встроенный оператор в разделе 5 указывает категорию ее ценности доходности и категории значений операндов, которых он ожидает. Например, встроенные операторы присваивания ожидают что левый операнд является lvalue и что правый операнд является prvalue и в результате получим lvalue. Определяемые пользователем операторы - это функции, и категории ценностей, которые они ожидание и доходность определяются их параметров и типов возврата. -конец примечание

Я предлагаю вам прочитать весь раздел 3.10 Lvalues ​​и rvalues ​​, хотя.

Как эти новые категории относятся к существующим категориям rvalue и lvalue?

Снова:

Taxonomy

Являются ли категории rvalue и lvalue в С++ 0x такими же, как и в С++ 03?

Семантика rvalues ​​развилась особенно с введением семантики перемещения.

Почему нужны эти новые категории?

Таким образом, конструкция/назначение перемещения можно определить и поддерживать.

Ответ 3

Я начну с вашего последнего вопроса:

Почему нужны эти новые категории?

Стандарт С++ содержит множество правил, относящихся к категории значений выражения. В некоторых правилах проводится различие между значениями lvalue и rvalue. Например, когда дело доходит до разрешения перегрузки. Другие правила делают различие между glvalue и prvalue. Например, вы можете иметь glvalue с неполным или абстрактным типом, но не существует prvalue с неполным или абстрактным типом. Прежде, чем мы получили эту терминологию, правила, которые действительно должны различать glvalue/prvalue, относящиеся к lvalue/rvalue, и они либо были непреднамеренно неправильными, либо содержали множество объяснений и исключений из правила a la "... если значение r должно быть вызвано неназванным rvalue reference...". Таким образом, кажется хорошей идеей просто дать понятия glvalues ​​и prvalues ​​собственное имя.

Что это за новые категории выражений? Как эти новые категории относятся к существующим категориям rvalue и lvalue?

Мы все еще имеем слова lvalue и rvalue, которые совместимы с С++ 98. Мы просто разделили rvalues ​​на две подгруппы, xvalues ​​и prvalues, и мы будем называть lvalues ​​и xvalues ​​как glvalues. Xvalues ​​являются новой категорией значений для неназванных ссылок rvalue. Каждое выражение является одним из трех: lvalue, xvalue, prvalue. Диаграмма Венна будет выглядеть так:

    ______ ______
   /      X      \
  /      / \      \
 |   l  | x |  pr  |
  \      \ /      /
   \______X______/
       gl    r

Примеры с функциями:

int   prvalue();
int&  lvalue();
int&& xvalue();

Но также не забывайте, что названные ссылки rvalue являются lvalues:

void foo(int&& t) {
  // t is initialized with an rvalue expression
  // but is actually an lvalue expression itself
}

Ответ 4

Почему нужны эти новые категории? Являются ли боги WG21 просто путают нас простых смертных?

Я не чувствую, что другие ответы (хорошо, хотя многие из них) действительно фиксируют ответ на этот конкретный вопрос. Да, эти категории и такие существуют, чтобы позволить семантику перемещения, но сложность существует по одной причине. Это одно нерушимое правило перемещения вещей в С++ 11:

Ты должен двигаться только тогда, когда это, безусловно, это безопасно.

Вот почему существуют эти категории: иметь возможность говорить о ценностях, где можно безопасно переходить от них, и говорить о том, где это не так.

В ранней версии ссылок r-value движение происходило легко. Слишком легко. Достаточно легко, что существует много возможностей для неявного перемещения вещей, когда пользователь на самом деле не имел в виду.

Вот обстоятельства, при которых безопасно что-то перемещать:

  1. Когда это временный или подобъект. (Prvalue)
  2. Когда пользователь явно сказал, чтобы переместить его.

Если вы это сделаете:

SomeType &&Func() { ... }

SomeType &&val = Func();
SomeType otherVal{val};

Что это делает? В более старых версиях спецификации до появления 5 значений это спровоцировало бы движение. Конечно. Вы передали ссылку rvalue на конструктор и, таким образом, привязываетесь к конструктору, который принимает ссылку rvalue. Это очевидно.

Там только одна проблема с этим; вы не просили его переместить. О, вы могли бы сказать, что && должно было быть ключом, но это не меняет того факта, что он нарушил правило. val не является временным, потому что временные имена не имеют имен. Возможно, вы продлевали срок службы временного, но это означает, что он не является временным; это как и любая другая переменная стека.

Если это не временное, и вы не просили его переместить, то перемещение происходит неправильно.

Очевидное решение состоит в том, чтобы сделать val значением lvalue. Это означает, что вы не можете перейти от него. Хорошо; он назвал, поэтому его значение.

Как только вы это сделаете, вы уже не можете сказать, что SomeType&& означает одно и то же. Теперь вы сделали различие между названными ссылками rvalue и неназванными ссылками rvalue. Ну, названные ссылки rvalue - lvalues; это было наше решение выше. Итак, что мы называем неназванными ссылками rvalue (возвращаемое значение из Func выше)?

Это не lvalue, потому что вы не можете перейти от lvalue. И мы должны иметь возможность двигаться, возвращая &&; как еще вы могли бы прямо сказать что-то переместить? Это то, что std::move возвращает, в конце концов. Это не rvalue (old-style), потому что это может быть в левой части уравнения (вещи на самом деле немного сложнее, см. Этот вопрос и комментарии ниже). Это ни lvalue, ни rvalue; это новый вид вещей.

У нас есть ценность, которую вы можете рассматривать как lvalue, за исключением того, что она неявно перемещается. Мы называем это значением x.

Обратите внимание, что xvalues - это то, что заставляет нас получать две другие категории значений:

  • Prvalue на самом деле просто новое имя для предыдущего типа rvalue, то есть они являются значениями, которые не являются значениями xvalues.

  • Glvalues - это объединение значений x и lvalues в одной группе, потому что они действительно имеют много общих свойств.

Так что действительно, все сводится к значениям x и необходимости ограничивать движение точно и только в определенных местах. Эти места определяются категорией rvalue; prvalues - это неявные движения, а xvalues - явные перемещения (std::move возвращает значение xvalue).

Ответ 5

ИМХО, лучшее объяснение его значения дало нам Страуструпу + принять во внимание примеры Даниэля Шандора и Мохана:

Страуструп:

Теперь я серьезно переживаю. Ясно, что мы собирались в тупик или беспорядок или оба. Я провел обеденный перерыв, анализируя, какие свойства (значений) были независимыми. Было только два независимых свойства:

  • has identity - то есть и адрес, указатель, пользователь может определить, идентичны ли две копии и т.д.
  • can be moved from - т.е. нам разрешено выходить к источнику "копии" в каком-то неопределенном, но действительном состоянии

Это привело меня к выводу, что существует ровно три вида значений (используя нотацию регулярных выражений с использованием заглавной буквы для обозначения отрицательного значения - я торопился):

  • iM: имеет личность и не может быть перемещен из
  • im: имеет идентичность и может быть перемещен из (например, результат преобразования lvalue в ссылку на rvalue)
  • Im: не имеет идентичности и может быть перемещен из.

    Четвертая возможность, IM, (не имеет идентичности и не может быть перемещена) бесполезна в C++ (или, я думаю) на любом другом языке.

В дополнение к этим трем основным классификациям значений у нас есть два очевидных обобщения, которые соответствуют двум независимым свойствам:

  • i: есть личность
  • m: может быть перемещен из

Это заставило меня поставить эту диаграмму на доске: enter image description here

Именование

Я заметил, что у нас была только ограниченная свобода имен: две точки слева (обозначенные iM и i) - это то, что люди с более или менее формальной lvalues назвали lvalues а две точки справа (обозначенные m и Im) - это то, что люди с более или менее формальной rvalues назвали rvalues. Это должно быть отражено в наших именах. То есть левая "нога" W должна иметь имена, связанные с lvalue а правая "нога" W должна иметь имена, связанные с rvalue. Я отмечаю, что вся эта дискуссия/проблема возникают из-за введения ссылок на rvalue и семантики перемещения. Эти понятия просто не существуют в мире Stracheys, состоящем только из rvalues и lvalues. Кто-то заметил, что идеи, которые

  • Каждое value является либо lvalue либо rvalue
  • lvalue не является rvalue и rvalue не является lvalue

глубоко укоренились в нашем сознании, очень полезные свойства, и следы этой дихотомии можно найти по всему проекту стандарта. Мы все согласились с тем, что мы должны сохранить эти свойства (и сделать их точными). Это еще больше ограничило наш выбор имен. Я заметил, что формулировка стандартной библиотеки использует rvalue для обозначения m (обобщение), так что для сохранения ожидания и текста стандартной библиотеки правая нижняя точка W должна быть названа rvalue.

Это привело к целенаправленному обсуждению имен. Сначала нам нужно было определиться с lvalue. Должно ли lvalue означать iM или обобщение i? Во главе с Дагом Грегором мы перечислили места в основной формулировке языка, где слово lvalue было квалифицировано для обозначения того или другого. Список был составлен, и в большинстве случаев и в самом хитром/хрупком тексте lvalue настоящее время означает iM. Это классическое значение lvalue, потому что "в старые времена" ничего не двигалось; move является новым понятием в C++0x. Кроме того, присвоение имени максимальной точке значения W lvalue дает нам свойство, что каждое значение является lvalue или rvalue, но не обоими.

Итак, верхняя левая точка W - это lvalue а нижняя правая точка - rvalue. Что это делает нижнюю левую и верхнюю правую точки? Нижняя левая точка является обобщением классического lvalue, позволяющего двигаться. Так что это generalized lvalue. Мы назвали это glvalue. Вы можете поспорить о сокращении, но (я думаю) не с логикой. Мы предполагали, что при серьезном использовании generalized lvalue каким-то образом будет сокращено, так что лучше сделать это немедленно (или рискнуть путаницей). Верхняя правая точка W является менее общей, чем нижняя правая (теперь, как всегда, называется rvalue). Эта точка представляет собой исходное чистое понятие объекта, с которого вы можете двигаться, потому что на него нельзя ссылаться снова (кроме как деструктором). Мне понравилась фраза specialized rvalue в отличие от generalized lvalue но pure rvalue сокращенное до prvalue победило (и, вероятно, это правильно). Таким образом, левая нога W является lvalue и glvalue а правая нога - prvalue и rvalue. Кстати, каждое значение является либо glvalue, либо prvalue, но не обоими.

Это оставляет верхнюю середину W: im; то есть значения, которые имеют идентичность и могут быть перемещены. У нас действительно нет ничего, что помогло бы нам найти хорошее имя для тех эзотерических зверей. Они важны для людей, работающих с (черновым) стандартным текстом, но вряд ли станут нарицательным. Мы не нашли каких-либо реальных ограничений для именования, чтобы направлять нас, поэтому мы выбрали x для центра, неизвестного, странного, только для xpert или даже с рейтингом x.

Steve showing off the final product

Ответ 6

ВСТУПЛЕНИЕ

ISO C++ 11 (официально ISO/IEC 14882: 2011) является самой последней версией стандарта языка программирования C++. Он содержит некоторые новые функции и концепции, например:

  • Rvalue ссылки
  • xvalue, glvalue, prvalue выражения значения категории
  • переместить семантику

Если мы хотим понять концепции новых категорий значений выражений, мы должны знать, что существуют ссылки на rvalue и lvalue. Лучше знать, что значения могут быть переданы неконстантным ссылкам.

int& r_i=7; // compile error
int&& rr_i=7; // OK

Мы можем получить некоторое представление о концепциях категорий значений, если процитируем подраздел "Значения и значения" из рабочего проекта N3337 (наиболее похожего на опубликованный стандарт ISO C++ 11).

3.10 Lvalues и rvalues [basic.lval]

1 Выражения классифицированы в соответствии с таксономией на рисунке 1.

  • Lvalue (исторически так называемое lvalue, которое может появляться в левой части выражения присваивания) обозначает функцию или объект. [Пример: если E является выражением типа указателя, тогда * E является выражением lvalue, ссылающимся на объект или функцию, на которые указывает E. В качестве другого примера, результатом вызова функции, тип возвращаемой которой является ссылкой на lvalue, является lvalue. - конец примера]
  • Значение xvalue (значение "eXpiring") также относится к объекту, обычно ближе к концу его времени жизни (например, чтобы его ресурсы могли быть перемещены). Xvalue - это результат некоторых видов выражений, включающих ссылки на rvalue (8.3.2). [Пример: Результатом вызова функции, тип возвращаемой которой является ссылкой на rvalue, является значение xvalue. - конец примера]
  • Glvalue ("обобщенное" lvalue) - это lvalue или xvalue.
  • Rvalue (исторически так называемое, потому что rvalue может появляться в правой части выражения присваивания) - это xvalue, a
    временный объект (12.2) или его подобъект, или значение, которое не является
    связанный с объектом.
  • Prvalue ("чистое" rvalue) - это rvalue, которое не является xvalue. [Пример: результат вызова функции, тип возвращаемой которой не является
    ссылка является prvalue. Значение литерала, такого как 12, 7.3e5 или
    истина также prvalue. - конец примера]

Каждое выражение относится к одной из основных классификаций в этой таксономии: lvalue, xvalue или prvalue. Это свойство выражения называется его категорией значений.

Но я не совсем уверен в том, что этого подраздела достаточно для четкого понимания концепций, потому что "обычно" не является действительно общим, "ближе к концу его жизненного цикла" не является действительно конкретным, "использование ссылок на значения" не совсем понятно, и "Пример. Результатом вызова функции, тип возвращаемой которой является ссылкой на rvalue, является значение xvalue". Похоже, змея кусает свой хвост.

ПЕРВИЧНАЯ ЦЕННОСТЬ КАТЕГОРИИ

Каждое выражение принадлежит ровно одной категории первичных значений. Этими категориями значений являются категории lvalue, xvalue и prvalue.

lvalues

Выражение E относится к категории lvalue тогда и только тогда, когда E относится к объекту, у которого УЖЕ есть идентификатор (адрес, имя или псевдоним), который делает его доступным вне E.

#include <iostream>

int i=7;

const int& f(){
    return i;
}

int main()
{
    std::cout<<&"www"<<std::endl; // The expression "www" in this row is an lvalue expression, because string literals are arrays and every array has an address.  

    i; // The expression i in this row is an lvalue expression, because it refers to the same entity ...
    i; // ... as the entity the expression i in this row refers to.

    int* p_i=new int(7);
    *p_i; // The expression *p_i in this row is an lvalue expression, because it refers to the same entity ...
    *p_i; // ... as the entity the expression *p_i in this row refers to.

    const int& r_I=7;
    r_I; // The expression r_I in this row is an lvalue expression, because it refers to the same entity ...
    r_I; // ... as the entity the expression r_I in this row refers to.

    f(); // The expression f() in this row is an lvalue expression, because it refers to the same entity ...
    i; // ... as the entity the expression f() in this row refers to.

    return 0;
}

xvalues

Выражение E относится к категории xvalue тогда и только тогда, когда оно

- результат вызова функции, неявно или явно, чей тип возврата является ссылкой на тип возвращаемого объекта, или

int&& f(){
    return 3;
}

int main()
{
    f(); // The expression f() belongs to the xvalue category, because f() return type is an rvalue reference to object type.

    return 0;
}

- приведение к rvalue ссылке на тип объекта, или

int main()
{
    static_cast<int&&>(7); // The expression static_cast<int&&>(7) belongs to the xvalue category, because it is a cast to an rvalue reference to object type.
    std::move(7); // std::move(7) is equivalent to static_cast<int&&>(7).

    return 0;
}

- выражение доступа к члену класса, обозначающее нестатический член данных не ссылочного типа, в котором объектное выражение является значением xvalue, или

struct As
{
    int i;
};

As&& f(){
    return As();
}

int main()
{
    f().i; // The expression f().i belongs to the xvalue category, because As::i is a non-static data member of non-reference type, and the subexpression f() belongs to the xvlaue category.

    return 0;
}

- выражение указателя на член, в котором первый операнд является значением x, а второй операнд - указателем на элемент данных.

Обратите внимание, что эффект приведенных выше правил заключается в том, что именованные ссылки на значения rvalue обрабатываются как lvalues, а безымянные ссылки на значения rvalue на объекты обрабатываются как xvalues; rvalue ссылки на функции обрабатываются как lvalues независимо от того, названы они или нет.

#include <functional>

struct As
{
    int i;
};

As&& f(){
    return As();
}

int main()
{
    f(); // The expression f() belongs to the xvalue category, because it refers to an unnamed rvalue reference to object.
    As&& rr_a=As();
    rr_a; // The expression rr_a belongs to the lvalue category, because it refers to a named rvalue reference to object.
    std::ref(f); // The expression std::ref(f) belongs to the lvalue category, because it refers to an rvalue reference to function.

    return 0;
}

prvalues

Выражение E относится к категории prvalue тогда и только тогда, когда E не принадлежит ни к lvalue, ни к категории xvalue.

struct As
{
    void f(){
        this; // The expression this is a prvalue expression. Note, that the expression this is not a variable.
    }
};

As f(){
    return As();
}

int main()
{
    f(); // The expression f() belongs to the prvalue category, because it belongs neither to the lvalue nor to the xvalue category.

    return 0;
}

СМЕШАННЫЕ ЗНАЧЕНИЯ КАТЕГОРИИ

Есть еще две важные категории смешанных значений. Этими категориями значений являются категории rvalue и glvalue.

rvalues

Выражение E принадлежит категории rvalue тогда и только тогда, когда E принадлежит категории xvalue или категории prvalue.

Обратите внимание, что это определение означает, что выражение E принадлежит категории rvalue тогда и только тогда, когда E относится к объекту, у которого нет какой-либо идентичности, которая делает его доступным вне E YET.

glvalues

Выражение E принадлежит категории glvalue тогда и только тогда, когда E принадлежит категории lvalue или категории xvalue.

ПРАКТИЧЕСКОЕ ПРАВИЛО

Скотт Мейер опубликовал очень полезное практическое правило, позволяющее отличать значения от значений.

  • Если вы можете взять адрес выражения, выражение будет lvalue.
  • Если тип выражения является ссылкой на lvalue (например, T & или const T & и т.д.), Это выражение является lvalue.
  • В противном случае выражение является значением. Концептуально (и обычно также фактически) значения соответствуют временным объектам, таким как возвращаемые из функций или созданные неявными преобразованиями типов. Большинство литеральных значений (например, 10 и 5.3) также являются r-значениями.

Ответ 7

Категории

С++ 03 слишком ограничены, чтобы правильно вносить ссылки rvalue в атрибуты выражений.

С их введением было сказано, что ссылка на неназванный rvalue оценивается как rvalue, так что разрешение перегрузки будет предпочтительнее привязки ссылок rvalue, что сделает его выборными конструкторами перемещения над конструкторами копирования. Но было обнаружено, что это вызывает проблемы вокруг, например, с Динамические типы и с квалификацией.

Чтобы показать это, рассмотрим

int const&& f();

int main() {
  int &&i = f(); // disgusting!
}

В предварительных черновиках это было разрешено, потому что в С++ 03 значения типов неклассов никогда не имеют квалификации. Но предполагается, что const применяется в случае с rvalue-reference, потому что здесь мы ссылаемся на объекты (= память!), А drop const из неклассических значений r главным образом по той причине, что вокруг объекта нет объекта.

Проблема для динамических типов имеет сходный характер. В С++ 03 значения типа класса имеют известный динамический тип - это статический тип этого выражения. Потому что, чтобы иметь это по-другому, вам нужны ссылки или различия, которые оцениваются до значения lvalue. Это неверно с неназванными ссылками rvalue, но они могут демонстрировать полиморфное поведение. Чтобы решить эту проблему,

  • ссылки без имени rvalue становятся xvalues ​​. Они могут быть квалифицированными и потенциально отличаться от их динамического типа. Они, как и предполагалось, предпочитают ссылки rvalue во время перегрузки и не свяжутся с неконстантными ссылками lvalue.

  • То, что раньше было rvalue (литералы, объекты, созданные приведениями к типам без ссылки), теперь становится prvalue. Они имеют то же предпочтение, что и значения x во время перегрузки.

  • То, что ранее было lvalue, остается lvalue.

И две группы выполняются для захвата тех, которые могут быть квалифицированы и могут иметь разные динамические типы (glvalues ​​) и те, где перегрузка предпочитает привязку ссылки rvalue ( rvalues ​​).

Ответ 8

Я долгое время боролся с этим, пока не наткнулся на объяснение cppreference.com категорий .

Это на самом деле довольно просто, но я нахожу, что его часто объясняют так, что трудно запомнить. Здесь это объясняется очень схематично. Я приведу некоторые части страницы:

Основные категории

Категории первичных значений соответствуют двум свойствам выражений:

  • имеет личность: можно определить, относится ли выражение к тому же объекту, что и другое выражение, например, путем сравнения адресов объектов или функций, которые они идентифицируют (получаются прямо или косвенно);

  • можно перенести из: move constructor, move assign operator или другую перегрузку функции, которая реализует семантику перемещения, может привязываться к выражению.

Выражения, которые:

  • имеют идентификатор и не могут быть перенесены из них, называются выражениями lvalue;
  • имеют личность и могут быть перемещены из называются выражениями xvalue;
  • не имеют идентификатора и могут быть перемещены из вызываемых выражений prvalue;
  • не имеют идентификатора и не могут быть перемещены из него.

именующего

выражение lvalue ( "left value" ) является выражением, которое имеет идентичность и не может быть перемещено.

rvalue (до С++ 11), prvalue (начиная с С++ 11)

выражение prvalue ( "pure rvalue" ) - это выражение, которое не имеет идентификатора и может быть перенесено из.

xvalue

Выражение xvalue ( "expired value" ) является выражением, которое имеет идентификатор и может быть перемещено из.

glvalue

выражение glvalue ( "generalized lvalue" ) является выражением, которое является либо значением lvalue, либо значением x. Он имеет идентичность. Он может быть или не быть перемещен из.

rvalue (поскольку С++ 11)

выражение rvalue ( "right value" ) является выражением, которое является либо значением prvalue, либо значением x. Его можно перенести. Он может иметь или не иметь идентичности.

Ответ 9

Как эти новые категории относятся к существующим категориям rvalue и lvalue?

A С++ 03 lvalue по-прежнему является значением С++ 11 lvalue, тогда как значение С++ 03 rvalue называется prvalue в С++ 11.

Ответ 10

Одно дополнение к превосходным ответам выше, на том, что меня смутило даже после того, как я прочитал "Страуструп" и подумал, что я понимаю разницу в значении/значении. Когда вы видите

int&& a = 3,

очень заманчиво читать int&& как тип и заключить, что a является значением r. Это не так:

int&& a = 3;
int&& c = a; //error: cannot bind 'int' lvalue to 'int&&'
int& b = a; //compiles

a имеет имя и ipso facto lvalue. Не думайте о && как о части a; это просто что-то говорит вам, к чему a разрешено связывать.

Это особенно важно для аргументов типа T&& в конструкторах. Если вы пишете

Foo::Foo(T&& _t) : t{_t} {}

вы скопируете _t в t. Вам нужно

Foo::Foo(T&& _t) : t{std::move(_t)} {}, если вы хотите переместить. Если бы мой компилятор предупредил меня, когда я оставил move!

Ответ 11

Поскольку предыдущие ответы исчерпывающе охватывали теорию, стоящую за категориями ценностей, я хотел бы добавить еще одну вещь: вы можете поиграть с ней и протестировать.

Для некоторых практических экспериментов с категориями значений вы можете использовать спецификатор decltype. Его поведение явно различает три категории основных значений (xvalue, lvalue и prvalue).

Использование препроцессора спасает нас от набора текста...

Основные категории:

#define IS_XVALUE(X) std::is_rvalue_reference<decltype((X))>::value
#define IS_LVALUE(X) std::is_lvalue_reference<decltype((X))>::value
#define IS_PRVALUE(X) !std::is_reference<decltype((X))>::value

Смешанные категории:

#define IS_GLVALUE(X) IS_LVALUE(X) || IS_XVALUE(X)
#define IS_RVALUE(X) IS_PRVALUE(X) || IS_XVALUE(X)

Теперь мы можем воспроизвести (почти) все примеры из cppreference по категориям значений.

Вот несколько примеров с С++ 17 (для краткого static_assert):

void doesNothing(){}
struct S
{
    int x{0};
};
int x = 1;
int y = 2;
S s;

static_assert(IS_LVALUE(x));
static_assert(IS_LVALUE(x+=y));
static_assert(IS_LVALUE("Hello world!"));
static_assert(IS_LVALUE(++x));

static_assert(IS_PRVALUE(1));
static_assert(IS_PRVALUE(x++));
static_assert(IS_PRVALUE(static_cast<double>(x)));
static_assert(IS_PRVALUE(std::string{}));
static_assert(IS_PRVALUE(throw std::exception()));
static_assert(IS_PRVALUE(doesNothing()));

static_assert(IS_XVALUE(std::move(s)));
// The next one doesn't work in gcc 8.2 but in gcc(trunk). Clang 7.0.0 and msvc 19.16 are doing fine.
static_assert(IS_XVALUE(S().x)); 

Смешанные категории становятся скучными, когда вы выяснили основную категорию.

Дополнительные примеры (и эксперименты) можно найти по следующей ссылке в проводнике компилятора. Не читайте сборку. Я добавил много компиляторов, чтобы убедиться, что он работает на всех распространенных компиляторах.

Ответ 12

В С++ переменные являются типом l-значения (произносится как ell-value). Значение l - это значение, которое имеет постоянный адрес (в памяти). Поскольку все переменные имеют адреса, все переменные являются значениями l. Имя l-значение возникло из-за того, что l-значения являются единственными значениями, которые могут быть в левой части оператора присваивания. Когда мы выполняем задание, левая часть оператора присваивания должна быть l-значением. Следовательно, утверждение типа 5 = 6; приведет к ошибке компиляции, поскольку 5 не является значением l. Значение 5 не имеет памяти, и, следовательно, ничто не может быть назначено ему. 5 означает 5, и его значение не может быть переназначено. Когда l-value имеет назначенное ему значение, текущее значение на этом адресе памяти перезаписывается.

Противоположностью l-значениям являются r-значения (выраженные arr-значения). Значение r относится к значениям, которые не связаны с постоянным адресом памяти. Примерами r-значений являются одиночные числа (например, 5, которые оцениваются до 5) и выражения (например, 2 + x, который оценивает значение переменной x плюс 2). r-значения обычно носят временный характер и отбрасываются в конце заявления, в котором они встречаются.

Ниже приведен пример некоторых операторов присваивания, показывающих, как оцениваются значения r:

int y;      // define y as an integer variable
y = 4;      // r-value 4 evaluates to 4, which is then assigned to l-value y
y = 2 + 5;  // r-value 2 + r-value 5 evaluates to r-value 7, which is then assigned to l-value y

int x;      // define x as an integer variable
x = y;      // l-value y evaluates to 7 (from before), which is then assigned to l-value x.
x = x;      // l-value x evaluates to 7, which is then assigned to l-value x (useless!)
x = x + 1;  // l-value x + r-value 1 evaluate to r-value 8, which is then assigned to l-value x.

Давайте рассмотрим последний оператор присваивания выше, поскольку он вызывает наибольшую путаницу.

x = x+1

В этом утверждении переменная x используется в двух разных контекстах. В левой части оператора присваивания "x" используется как l-значение (переменная с адресом), в котором сохраняется значение. С правой стороны оператора присваивания x вычисляется для получения значения (в данном случае 7). Когда С++ оценивает вышеуказанный оператор, он оценивается как:

x = 7+1

Что делает очевидным, что С++ присваивает значение 8 обратно переменной x.

В настоящее время вам не нужно много беспокоиться о значениях l или значениях r, но лучше вернуться к ним позже, когда мы начнем обсуждение более продвинутых тем.

Ключевым выводом здесь является то, что в левой части задания вы должны иметь то, что представляет собой адрес памяти (например, переменную). Все, что находится в правой части задания, будет оценено для получения значения.