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

1357

В C ++ 03 выражение является либо значением r, либо значением l .

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

  1. Rvalue
  2. именующий
  3. xvalue
  4. glvalue
  5. prvalue

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

  • Каковы эти новые категории выражений?
  • Как эти новые категории связаны с существующими категориями rvalue и lvalue?
  • Являются ли категории rvalue и lvalue в C ++ 0x такими же, как в C ++ 03?
  • Зачем нужны эти новые категории? Являются WG21 боги просто пытаются запутать нас , простых смертных?
Джеймс МакНеллис
источник
9
@Philip Поттер: В C ++ 03? Да. Lvalue может использоваться в качестве rvalue, потому что существует стандартное преобразование lvalue в rvalue.
Джеймс МакНеллис
14
@Tyler: «Если вы можете присвоить ему, это lvalue, в противном случае, это rvalue». -> Неправильно, вы можете назначить класс rvalues: string("hello") = string("world").
fredoverflow
4
Обратите внимание, что это категория значения. Есть несколько свойств, которые могут иметь выражения. К ним относятся битовое поле (true / false), временное (true / false) и тип (его тип).
Йоханнес Шауб -
30
Я думаю, что ссылка Фреда выше, чем любой из ответов здесь. Ссылка мертва, хотя. Он был перемещен по адресу
Р. Мартиньо Фернандес,
74
в C ++ даже ваши типы имеют типы
nielsbot

Ответы:

634

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

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

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

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

Цитирование n3055 :

  • - Значение (так называемый, исторически, поскольку lvalues может появиться на левой стороне выражения присваивания) обозначает функцию или объект. [Пример: If Eявляется выражением типа указателя, тогда *E является выражением lvalue, относящимся к объекту или функции, на которые E указывает. В качестве другого примера, результатом вызова функции, тип возвращаемой которой является ссылкой на lvalue, является lvalue.]
  • Xvalue (ое «истекающее» значение) также относится к объекту, как правило , ближе к концу своей жизни (так , чтобы его ресурсы могут быть перемещены, например). Xvalue - это результат некоторых видов выражений, включающих ссылки на rvalue. [Пример: результатом вызова функции, тип возвращаемой которой является ссылкой на rvalue, является xvalue.]
  • Glvalue ( «обобщенный» именующая) является именующим или xvalue .
  • Rvalue (так называемая, исторически, потому rvalues может появиться на правой стороне выражения присваивания) является xvalue, временный объект или подобъект его, или значение , которое не связано с объектом.
  • Prvalue ( «чистый» Rvalue) является Rvalue , что не является xvalue. [Пример: результат вызова функции, тип возврата которой не является ссылкой, является предварительным значением]

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

Корнел Киселевич
источник
Спасибо, этот ответ действительно полезен! Но мой компилятор не согласен с вашими примерами для xvalues ​​и prvalues; они полная противоположность. Возврат по ссылке rvalue дает мне prvalue, а возврат по значению дает мне xvalue. Вы их перепутали, или мой испытательный стенд сломан? Я попробовал это с GCC 4.6.1, clang (из svn) и MSVC, и все они показывают одинаковое поведение.
Ким Грасман
Ой, я просто перешел по ссылке и заметил, что примеры есть в источнике. Я пойду, найду свою копию стандарта и проверю, что там написано ...
Ким Грасман
4
Здесь я использую макросы для проверки различных выражений: stackoverflow.com/a/6114546/96963 Возможно, они ошибаются в диагностике.
Ким Грасман
1
Добавление xvalue не для семантики перемещения. Только с lvalue и rvalue семантика перемещения, совершенная передача вперед и ссылка на rvalue по-прежнему работают хорошо. Я думаю, что xvalue только для оператора decltype: если выражение операнда - xvalue, decltype дает тип ссылки на rvalue.
лиганд
1
@MuhamedCicak «Каждое выражение является либо lvalue, либо rvalue»: это правда; и стандарт (или документ n3055) не говорит, что это ложно. Причина, по которой это предложение было вычеркнуто, заключается в том, что вы просматривали изменения между двумя версиями документа. Предложение было удалено, потому что оно стало излишним после добавления более точного объяснения.
максимум
337

Каковы эти новые категории выражений?

FCD (n3092) имеет превосходное описание:

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

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

- glvalue («обобщенное» lvalue) - это lvalue или xvalue.

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

- prvalue («чистое» rvalue) - это rvalue, которое не является xvalue. [Пример: результат вызова функции, тип возвращаемой которой не является ссылкой, является предварительным значением. Значение литерала, такого как 12, 7.3e5 или true, также является prvalue. - конец примера]

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

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

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

Снова:

таксономия

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

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

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

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

dirkgently
источник
54
Мне нравится схема здесь. Я думаю, что было бы полезно начать ответ с «Каждое выражение принадлежит точно к одной из фундаментальных классификаций в этой таксономии: lvalue, xvalue или prvalue». Тогда легко использовать диаграмму, чтобы показать, что эти три фундаментальных класса объединены в glvalue и rvalue.
Аарон МакДейд
2
"is glvalue" эквивалентно "is not prvalue", а "is rvalue" эквивалентно "is not lvalue".
Владимир Решетников
2
Этот помог мне больше всего: bajamircea.github.io/assets/2016-04-07-move-forward/… (диаграмма ценностных категорий Венна)
Джон Р
1
@AaronMcDaid Привет, быстрый вопрос, если вы / кто-то может ответить ... Почему бы не назвать glvalueкак lvalueи lvalueкак plvalue, чтобы быть последовательным?
Виджей Чавда
184

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

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

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

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

У нас все еще есть термины lvalue и rvalue, которые совместимы с C ++ 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
}
sellibitze
источник
165

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

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

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

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

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

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

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

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

SomeType &&Func() { ... }

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

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

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

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

Очевидное решение - сделать vallvalue. Это означает, что вы не можете отойти от этого. Хорошо; это имя, так что это lvalue.

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

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

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

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

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

  • Glvalues ​​- это объединение xvalues ​​и lvalues ​​в одну группу, потому что они имеют много общих свойств.

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

Николь Болас
источник
11
@ Томас: Это пример; не имеет значения, как он создает возвращаемое значение. Важно то, что он возвращает &&.
Никол Болас
1
Примечание: значения могут быть в левой части уравнения, а также - как в X foo(); foo() = X;... По этой фундаментальной причине я не могу полностью следовать вышеприведенному превосходному ответу до конца, потому что вы действительно делаете различие только между новое xvalue и prvalue старого стиля, основанные на том факте, что оно может быть на lhs.
Дан Ниссенбаум
1
Xбыть классом; X foo();быть объявлением функции, и foo() = X();быть строкой кода. (Я оставил второй набор скобок в foo() = X();своем комментарии выше.) Вопрос, который я только что опубликовал с выделенным этим использованием, см. На stackoverflow.com/questions/15482508/…
Дан Ниссенбаум
1
@DanNissenbaum "xvalue не может быть в левой части выражения присваивания" - почему бы и нет? Смотрите ideone.com/wyrxiT
Михаил
1
Просветляющий ответ. Это, несомненно, лучший ответ здесь. Это дало мне обоснование введения новых категорий ценностей и того, что произошло раньше.
Никос
136

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

Страуструп:

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

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

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

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

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

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

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

Это заставило меня поставить эту диаграмму на доске: введите описание изображения здесь

Именование

Я заметил, что у нас была только ограниченная свобода имен: две точки слева (помеченные iMи i) - это то, что люди с более или менее формальной формой называли, lvaluesа две точки справа (помеченные mи Im) - это то, что люди с более или менее формальной формальностью позвонил rvalues. Это должно быть отражено в наших именах. То есть левая «нога» Wдолжна иметь имена, связанные с, lvalueа правая «нога» Wдолжна иметь имена, связанные с тем, что rvalue.я отмечаю, что вся эта дискуссия / проблема возникают из-за введения ссылок на rvalue и семантики перемещения. Эти понятия просто не существуют в мире Стрейчи, состоящем из справедливых 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, но не оба.

Итак, верхняя левая точка Wis 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; то есть значения, которые имеют идентичность и могут быть перемещены. У нас действительно нет ничего, что помогло бы нам найти хорошее имя для тех эзотерических зверей. Они важны для людей, работающих с (черновым) стандартным текстом, но вряд ли станут нарицательным. Мы не нашли каких-либо реальных ограничений в именовании, чтобы направлять нас, поэтому мы выбрали «х» для центра, неизвестного, странного, только xpert или даже с рейтингом х.

Стив хвастается конечным продуктом

Иван Куш
источник
14
да, лучше читать оригинальные предложения и обсуждения комитета C ++, чем стандарт, если вы хотите понять, что они имели в виду: D
Иван Куш,
8
Литералы не имеют идентичности и не могут быть перемещены; они тем не менее полезны.
DrPizza
Я просто хочу уточнить кое-что. int && f () {return 1; } и MyClass && g () {return MyClass (); } вернуть xvalue, верно? Тогда где я могу найти тождество выражений f (); и "g ();"? Они имеют идентичность, потому что в выражении возврата есть другое выражение, которое ссылается на тот же объект, на который они ссылаются - правильно ли я понимаю?
Даниэль Шандор
6
@DrPizza Согласно стандарту: строковые литералы - это lvalues, все остальные литералы - это prvalues. Строго говоря, вы можете привести аргумент в пользу того, что нестроковые литералы должны быть неподвижными, но стандарт написан не так.
Брайан Ванденберг
59

ВВЕДЕНИЕ

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

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

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

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

Мы можем получить некоторое представление о концепциях категорий значений, если процитируем подраздел «Значения и значения» из рабочего проекта N3337 (наиболее похожего на опубликованный стандарт ISOC ++ 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.
  • Значение r (исторически так называемое, поскольку значения r могут появляться в правой части выражения присваивания) представляет собой значение x,
    временный объект (12.2) или его подобъект или значение, которое не
    связано с объектом.
  • Prvalue («чистое» rvalue) - это rvalue, которое не является xvalue. [Пример: результат вызова функции, тип возвращаемой которой не является
    ссылкой, является предварительным значением. Значение литерала, такого как 12, 7.3e5 или
    true, также является 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-значениями.
Даниэль Шандор
источник
3
Все примеры для lvalues ​​и все примеры для xvalues ​​также являются примерами для glvalues. Спасибо за редактирование!
Даниэль Шандор
1
Вы правы. Достаточно трех основных категорий стоимости. Rvalue также не является необходимым. Я думаю, что rvalue и glvalue в стандарте для удобства.
Даниэль Шандор
1
Если бы трудное время , чтобы понять переменная является prvalue. Я думал, что должно быть lvalue. До тех пор, пока стандарт 9.3.2 не скажет: в теле нестатической (9.3) функции-члена ключевое слово this является выражением prvalue. struct As{void f(){this;}}thisthis
r0ng
3
@ r0ng thisявляется prvalue, но *thislvalue
Xeverous
1
"www" не всегда имеет один и тот же адрес. Это lvalue, потому что это массив .
растяпа
35

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

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

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

int const&& f();

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

В черновиках до xvalue это было разрешено, потому что в C ++ 03 r-значения не-классовых типов никогда не были cv-квалифицированными. Но предполагается , что constприменяется в Rvalue-справочном случае, потому что здесь мы действительно относятся к объектам (= память!), И опуская сопзЬ из неклассовых rvalues в основном по той причине , что не существует ни одного объекта вокруг.

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

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

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

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

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

Йоханнес Шауб - Литб
источник
1
ответ очевидно разумный. xvalue - это просто rvalue, который может быть cv-квалифицированным и динамически типизированным!
лиганд
26

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

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

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

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

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

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

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

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

именующий

Выражение lvalue («левое значение») - это выражение, которое имеет идентичность и не может быть перемещено из .

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

Выражение prvalue («pure rvalue») - это выражение, которое не имеет идентичности и может быть перемещено из .

xvalue

Выражение xvalue («expiring value») - это выражение, которое имеет идентичность и может быть перемещено из .

glvalue

Выражение glvalue («обобщенное lvalue») - это выражение, которое является либо lvalue, либо xvalue. У него есть личность . Это может или не может быть перенесено из.

значение (начиная с C ++ 11)

Выражение rvalue («правильное значение») - это выражение, являющееся либо prvalue, либо xvalue. Это может быть перемещено из . Он может иметь или не иметь идентичность.

Феликс Домбек
источник
1
В некоторых книгах показано, что xvalues ​​имеют свои x от «expert» или «
extra
И что еще более важно, их полный список примеров.
Сиро Сантилли 郝海东 冠状 病 六四 事件 法轮功
19

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

Значение C ++ 03 все еще остается значением C ++ 11, тогда как значение C ++ 03 называется prvalue в C ++ 11.

fredoverflow
источник
14

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

int&& a = 3,

очень заманчиво читать int&&как тип и делать вывод, что aэто значение. Это не:

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!

Mohan
источник
1
Я думаю, что этот ответ можно уточнить. «Что aразрешено связывать»: Конечно, но в строках 2 и 3 ваши переменные - это c & b, и это не тот, который связывает, а тип aздесь не имеет значения, не так ли? Линии были бы такими же, если aбыл объявлен int a. На самом деле главное отличие состоит в том, что в строке 1 a не обязательно указывать const3.
Феликс Домбек
12

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

Для некоторых практических экспериментов с категориями значений вы можете использовать спецификатор 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 по категориям значений .

Вот несколько примеров с C ++ 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 9.1. Clang 7.0.0 and msvc 19.16 are doing fine.
static_assert(IS_XVALUE(S().x)); 

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

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

thebrandre
источник
Я думаю, что на #define IS_GLVALUE(X) IS_LVALUE(X) || IS_XVALUE(X)самом деле следует #define IS_GLVALUE(X) (IS_LVALUE(X) || IS_XVALUE(X))иначе посмотреть, что произойдет, если вы &&двое IS_GLVALUE.
Габриэль Devillers