Является ли член структуры rvalue rvalue или lvalue? - PullRequest
26 голосов
/ 08 февраля 2010

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

struct A
{
    int v;
};

struct A fun()
{
    struct A tmp;
    return tmp;
}

int main()
{
    fun().v = 1;
}

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

Ответы [ 6 ]

13 голосов
/ 08 февраля 2010

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

Стандарт заявляет в 5.3.5 [expr.ref]:

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

2 голосов
/ 04 апреля 2017

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

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

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

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

Xvalues ​​, которые также являются rvalues, определяются так

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

Помимо значений rval, еще одной зонтичной категорией значений является glvalue двух типов: xvalues ​​ и традиционные lvalues ​​.

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

enter image description here

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

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

2 голосов
/ 08 февраля 2010

Редактировать: Хорошо, наверное, у меня наконец-то есть что-то стандартное:

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

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

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

fun1() должен вернуть ссылку. Тип возврата функции без ссылки / указателя - это r-значение.

3.10 L-значения и значения

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

Таким образом, fun1().v является значением.

8.3.2 Ссылки

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

0 голосов
/ 08 февраля 2010

Это становится очевидным, если учесть, что компилятор сгенерирует для вас конструктор по умолчанию, конструктор копирования по умолчанию и оператор назначения копирования по умолчанию, если ваш 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 временно, тогда произойдет ошибка компиляции.

Чтобы завершить пример, вот как вызвать члена 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
}

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

edit: И вот обязательные цитаты:

12,2 Временные объекты:

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

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

3.10 L-значения и значения:

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

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

0 голосов
/ 08 февраля 2010

Я заметил, что у gcc очень мало вычислений относительно использования значений rvalue в качестве значений lval в выражениях присваивания. Это, например, компилируется просто отлично:

class A {
};

extern A f();

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

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

extern int f();

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

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

0 голосов
/ 08 февраля 2010

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

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

new A()

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

A* f(){ ...

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

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