Является ли членом структуры rval значение rvalue или lvalue?

Вызов функции, возвращающий структуру, является выражением rvalue, но как же его члены?
Этот fragment кода хорошо работает с моим компилятором g ++, но gcc дает сообщение об ошибке «lvalue, требуемое как левый операнд назначения»:

struct A { int v; }; struct A fun() { struct A tmp; return tmp; } int main() { fun().v = 1; } 

gcc рассматривает fun().v как rvalue, и я могу это понять.
Но g ++ не считает, что выражение присваивания неверно. Означает ли это fun1 (). V является lvalue в C ++?
Теперь проблема в том, что я искал стандарт C ++ 98/03, ничего не узнавая о том, является ли fun().v lvalue или rvalue.
Итак, что это?

Элементом выражения rvalue является rvalue.

Стандартные состояния в 5.3.5 [expr.ref]:

Если объявлено, что E2 имеет тип «ссылка на T», то E1.E2 является lvalue […] – если E2 является нестатическим членом данных, а тип E1 – «cq1 vq1 X», а тип E2 является «cq2 vq2 T», выражение обозначает именованный элемент объекта, обозначенный первым выражением. Если E1 – значение l, то E1.E2 является l значением.

Edit: Хорошо, я догадываюсь, что у меня есть что-то из стандарта:

Обратите внимание, что v имеет тип int который имеет встроенный оператор присваивания:

13.3.1.2. Операторы в выражениях

4 Для встроенных операторов присваивания преобразования левого операнда ограничены следующим образом: – не введены временные слова для удерживания левого операнда и […]

fun1() должен вернуть ссылку. Возвращаемый тип ссылки без ссылки / указателя является значением r.

3.10 Lvalues ​​и rvalues

5 Результатом вызова функции, которая не возвращает ссылку lvalue, является rvalue […]

Таким образом, fun1().v – значение r.

8.3.2 Ссылки

2 Тип ссылки, который объявляется с использованием &, называется ссылкой lvalue, а ссылочный тип, объявленный с использованием &&, называется ссылкой rvalue. Ссылки Lvalue и ссылки rvalue – это разные типы.

Это хорошее время, чтобы узнать, что такое xvalues glvalues .

Rvalues могут быть двух типов: prvalues и xvalues . Согласно новому стандарту C ++ 17

Prvalue – это выражение, чья оценка инициализирует объект, бит-поле или операнд оператора, как определено контекстом, в котором он появляется.

поэтому что-то вроде fun() в вашем примере оценивает значение prvalue (которое является rvalue). Это также говорит нам, что fun().v не является prvalue, так как это не инициализация ванили.

Xvalues, которые также являются значениями r, определяются так же

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

В дополнение к rvalues ​​другая категория значений зонтика – это значение glvalue, которое имеет два типа x значений и традиционные lvalues .

На данный момент мы определили основные категории ценностей. Это можно визуализировать так

введите описание изображения здесь

Категорий glvalue можно в целом рассматривать как означающие значения lvalues, прежде чем перемещение семантики стало вещью – вещь, которая может быть в левой части выражения. glvalue означает обобщенное значение lvalue.

Если мы посмотрим на определение xvalue , тогда он говорит, что что-то является значением xvalue, если оно близко к концу его жизни. В вашем примере fun().v находится ближе к концу его жизни. Таким образом, его ресурсы могут быть перемещены. И поскольку его ресурсы можно перемещать, это не значение lvalue, поэтому ваше выражение подходит только к единственной категории значений листьев – xvalue .

Я заметил, что gcc имеет очень мало компромиссов в отношении использования rvalues ​​как lvalues ​​в выражениях присваивания. Это, например, компилируется просто отлично:

 class A { }; extern A f(); void g() { A myA; f() = myA; } 

Почему это законно, и это не так (т.е. он не компилируется), хотя меня действительно смущает:

 extern int f(); void g() { f() = 5; } 

ИМХО, у стандартного комитета есть некоторые объяснения, связанные с lvalues, rvalues ​​и где они могут быть использованы. Это одна из причин, почему меня так интересует этот вопрос о rvalues .

Это становится очевидным, если вы считаете, что компилятор сгенерирует конструктор по умолчанию, конструктор копии по умолчанию и оператор присваивания по умолчанию для вас, если ваш struct / class не содержит ссылочные элементы. Затем подумайте, что стандарт позволяет вам вызывать методы-члены во временных рядах, то есть вы можете вызывать неконстантные члены в неконстантных временных рядах.

См. Этот пример:

 struct Foo {}; Foo foo () { return Foo(); } struct Bar { private: Bar& operator = (Bar const &); // forbid }; Bar bar () { return Bar(); } int main () { foo() = Foo(); // okay, called operator=() on non-const temporarie bar() = Bar(); // error, Bar::operator= is private } 

Если вы пишете

 struct Foo {}; const Foo foo () { // return a const value return Foo(); } int main () { foo() = Foo(); // error } 

т.е. если вы разрешите функции foo () возвращать константу , то возникает ошибка компиляции.

Чтобы сделать пример завершенным, вот как вызвать член const temporarie:

 struct Foo { int bar () const { return 0xFEED; } int frob () { return 0xFEED; } }; const Foo foo () { return Foo(); } int main () { foo().bar(); // okay, called const member method foo().frob(); // error, called non-const member of const temporary } 

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

edit: И вот требуемые цитаты:

12.2 Временные объекты:

  • 3) […] Временные объекты уничтожаются как последний шаг при оценке полного выражения (1.9), который (лексически) содержит точку, в которой они были созданы. […]

и затем (или лучше, раньше)

3.10 Lvalues ​​и rvalues:

  • 10) Для изменения объекта требуется lvalue для объекта, за исключением того, что rvalue типа classа также может использоваться для изменения его референта при определенных обстоятельствах. [Пример: функция-член, вызываемая для объекта (9.3), может изменять объект. ]

Пример использования: http://en.wikibooks.org/wiki/More_C%2B%2B_Idioms/Named_Parameter

У вас нет сцены. Возвращенная структура распределяется по стеку, поэтому результат присваивания немедленно теряется.

Ваша функция должна выделять новый экземпляр A:

 new A() 

В этом случае лучшая подпись

 A* f(){ ... 

Или верните существующий экземпляр, например:

 static A globalInstance; A& f(){ return globalInstance; }