Является ли член структуры rvalue rvalue или lvalue?

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


person hpsMouse    schedule 08.02.2010    source источник
comment
Надеюсь, вы не против, но я сделал так, чтобы копипаст было легко.   -  person GManNickG    schedule 08.02.2010
comment
Замечание: в C ++ вы не пишете структуру каждый раз, а только там, где она определена. Итак, вместо struct A fun () do A fun () вместо struct A tmp; do A tmp; < / я>   -  person Sebastian Mach    schedule 08.02.2010
comment
@phresnel: Я был склонен прокомментировать то же самое, но помните, что он тестирует это и на C.   -  person GManNickG    schedule 08.02.2010
comment
@phresnel: Как примечание, написание ключевого слова 'struct' необязательно в C ++ большую часть времени, но оно может потребоваться в некоторых конкретных случаях и имеет немного другое значение (поиск имени будет отличаться от 'struct' присутствуют или нет). Не то, чтобы в этом небольшом тесте это оказало влияние, но «в С ++ вы не пишете структуру каждый раз, если она вам не нужна»   -  person David Rodríguez - dribeas    schedule 08.02.2010
comment
@ *: ИМО, вопрос действительно должен быть озаглавлен: Сами ли члены временного rvalues? Комментарии? Хороший вопрос, кстати.   -  person dirkgently    schedule 08.02.2010
comment
@GMan: Я помню, как где-то упоминался C, но не могу найти его заново. Теперь все, что я вижу, это g ++, c ++ и C ++ 98/03: S   -  person Sebastian Mach    schedule 08.02.2010
comment
@David Rodríguez - dribeas: я не уверен, я никогда не слышал о том, чтобы присутствие struct изменило поиск по имени; имя структуры / класса / объединения является необязательным, и, если оно опущено, эти члены анонимного объекта будут участвовать в поиске имени охватывающей области, но поиск имени, зависящего от структуры, будет для меня новым, и я могу ' не найду что-нибудь в стандарте.   -  person Sebastian Mach    schedule 08.02.2010
comment
@ Дэвид Родригес - dribeas: и: Где это будет нужно? Мне не удалось найти пример для C ++.   -  person Sebastian Mach    schedule 08.02.2010
comment
Классический пример: у вас есть и функция, и структура с именем Foo. В этом случае Foo относится к функции, а struct Foo - к типу.   -  person MSalters    schedule 08.02.2010
comment
@phresnel: struct f {}; void f(); void g() { f(); struct f a; }. Вы можете прочитать подробное объяснение здесь: stackoverflow.com/questions / 1675351 /   -  person David Rodríguez - dribeas    schedule 08.02.2010
comment
@ Дэвид Родригес - dribeas: Спасибо, теперь я думаю, что вспомнил, как упоминалось в Josuttis / Vandevoorde :)   -  person Sebastian Mach    schedule 08.02.2010


Ответы (6)


Член выражения rvalue - это rvalue.

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

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

person David Rodríguez - dribeas    schedule 08.02.2010
comment
+1 Это один точный ответ. Выражение E1 равно fun() в исходном примере, E2 равно v, без уточнения cv. v также не является статическим и не является справочным. - person MSalters; 08.02.2010
comment
Я бы не нашел его, если бы вы не настаивали на большей точности первого ответа (который я сейчас удаляю). - person David Rodríguez - dribeas; 08.02.2010
comment
Раздел 5.2.5. Чтобы быть анальным, этот абзац ничего не определяет в случае, если E1 не является lvalue. Однако C ++ 0x добавляет иначе, это rvalue. - person Potatoswatter; 09.02.2010
comment
Я тоже прочитал эту часть стандарта. Он ничего не говорит о том, что происходит, когда E1 не является lvalue. Вот почему я так запутался ... И если C ++ 0x говорит, что это rvalue, он отвечает на вопрос. Может, это просто небольшая ошибка текущего стандарта? :) - person hpsMouse; 09.02.2010
comment
Он был изменен в следующем стандарте, начиная с DR421, так что есть дополнительное «иначе, это rvalue», как указывает Potatoswatter. Согласно стандартному комитету, несмотря на неудачную формулировку, намерение было: anubis.dkuug.dk/jtc1/sc22/wg21/docs/cwg_defects.html#421 - person David Rodríguez - dribeas; 09.02.2010
comment
Текущий проект C ++ 17 немного более ясен: If E1 is an lvalue, then E1.E2 is an lvalue; otherwise E1.E2 is an xvalue - person Arnaud; 20.11.2016

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

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

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

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

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

3.10 Значения L и r

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

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

8.3.2 Ссылки

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

person dirkgently    schedule 08.02.2010
comment
Хм? Он должен вернуть ссылку на временную? - person GManNickG; 08.02.2010
comment
Если это lvalue, почему вы можете ему присвоить? А ссылки на rvalue еще не являются частью официального стандарта C ++. - person Omnifarious; 08.02.2010
comment
Нет сомнений в том, что fun1() - это rvalue согласно 3.10, но, пожалуйста, прочтите вопрос. Выражение fun1().v не является результатом вызова функции. Он разбирается как (fun1()) . x - доступ к члену. - person MSalters; 08.02.2010
comment
@MSalters: отредактированный пост, который, как мне кажется, помогает. - person dirkgently; 08.02.2010
comment
@MSalters: есть ли причина полагать, что для объекта rvalue агрегированного типа его члены могут быть lvalue? - person dirkgently; 08.02.2010
comment
Выражение доступа к члену (ObjectExpr).member, очевидно, является либо rvalue, либо lvalue, и интуитивно понятно, что это будет rvalue, если (ObjectExpr) является rvalue. Но где это правило в стандарте? Интуиции недостаточно. Обратите внимание, что я не согласен с вами, что fun1() является выражением rvalue. - person MSalters; 08.02.2010
comment
Я добавил в стандарт пункт, в котором говорится, что результатом разыменования указателя элемента данных будет только lvalue, если объект имеет lvalue. Это не совсем то же самое, но поведение должно быть таким же. - person David Rodríguez - dribeas; 08.02.2010
comment
@dribeas: В вашем ответе? Звучит достаточно близко. C&V, пожалуйста, не могу его найти :( Кроме того, FWIW, обратите внимание, что никогда не бывает преобразования rvalue-to-lvalue (хотя преобразование lvalue-to-rvalue разрешено). - person dirkgently; 08.02.2010
comment
5.3.5 [expr.ref], см. Сообщение ниже. - person MSalters; 08.02.2010

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

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

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

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

Значения X, которые также являются значениями r, определяются следующим образом

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

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

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

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

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

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

person Curious    schedule 04.04.2017

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

class A {
};

extern A f();

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

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

extern int f();

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

IMHO, у стандартного комитета есть некоторые объяснения относительно lvalues, rvalues ​​и того, где их можно использовать. Это одна из причин, по которой меня так интересует этот вопрос о rvalues ​​< / а>.

person Omnifarious    schedule 08.02.2010
comment
Это действительно интересно ... Я бы сказал, что компилятор неправильный, но я понимаю, как они туда попали. Второй блок явно запрещен стандартом: вы не можете изменять rvalue, а в 5.17 / 1 для оператора присваивания требуется lvalue в качестве первого аргумента. Теперь стандарт позволяет вам вызывать непостоянные методы для значений r, и я могу только предположить, что после генерации оператора присваивания компилятор переводит первый блок в: f().operator=(myA) и рассматривает operator= как обычный метод, независимо от Все [=] требуют изменяемого lvalue в 5.17 / 1 - person David Rodríguez - dribeas; 08.02.2010
comment
@David: Считаете ли вы, что если кто-то явно сделал f().operator=(myA), это следует считать законным? - person Omnifarious; 08.02.2010
comment
Нет, но я вижу, что кто-то предполагает, что, поскольку operator= является функцией-членом (даже если она выполняет модификации), ей должно быть разрешено вызывать ее с lvalue. class X { void m(); }; X f(); void h() { f().m() } - допустимый фрагмент кода. Проблема для меня в том, что даже если это правда, разрешение вызова operator= на lvalue окажется несовместимым с примитивными типами (где стандарт ясен), поэтому, если бы я позвонил, я бы этого не допустил. В конце концов, даже если operator= является функцией-членом, она особенная - person David Rodríguez - dribeas; 08.02.2010
comment
@Omnifarious - я думаю, ваш код показывает, почему в целом рекомендуется возвращать const A, поскольку в этом случае нет расхождений между примитивными типами и типами, определяемыми пользователем. Обычно это рекомендуется в контексте перегрузки оператора (например, чтобы оператор + (T, T) возвращал const T), но я думаю, что это применимо к любой функции, возвращающей определенный пользователем тип. - person Manuel; 08.02.2010
comment
@Manuael: Раньше я соглашался с вами, но со ссылками на rvalue в C ++ 0x, где это оставить? Я хочу, чтобы возвращаемые значения можно было использовать как неконстантные ссылки rvalue. - person Omnifarious; 08.02.2010
comment
На самом деле я не вижу проблем с этим примером. Для типа класса присвоение выполняется членом operator =. Если пользовательский оператор = отсутствует, создается оператор по умолчанию. Поскольку для вызова функции-члена не требуется lvalue, всегда нормально выполнять присваивание по типу класса, если это явно не запрещено с помощью частного оператора =. А int - это встроенный тип, поэтому оператора-члена нет. - person hpsMouse; 09.02.2010
comment
@ DavidRodríguez-dribeas Проблема для меня в том, что даже если это правда, разрешение вызова operator = для lvalue окажется несовместимым с примитивными типами А как насчет + =? Те же рассуждения? - person curiousguy; 11.10.2011

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

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

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
}

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

изменить: И вот необходимые цитаты:

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

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

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

3.10 Значения L и r:

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

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

person Sebastian Mach    schedule 08.02.2010
comment
Думаю, у вас то же замешательство, что и у меня. См. Мой комментарий к этому ответу: stackoverflow.com/questions/2220230/ - person Manuel; 08.02.2010
comment
@Manuel: Тб, я не понимаю, почему ты думаешь, что я запутался !? - person Sebastian Mach; 08.02.2010
comment
@phreshel - Прочтите комментарий, на который я дал ссылку, и ответ dribeas. Ваши рассуждения верны, но здесь неприменимы, потому что v имеет примитивный тип. - person Manuel; 08.02.2010
comment
Когда я думаю об этом, становится интересно. Вызов функции-члена для объекта rvalue разрешен, а для вызова функции-члена объект должен быть привязан к этому указателю, который изменяет объект на lvalue во время выполнения вызова функции. Это означает, что объект rvalue иногда может стать lvalue. Но доступ к переменным-членам немного отличается и в стандарте не упоминается. И, кстати, я ненавижу это при определенных обстоятельствах. :-) - person hpsMouse; 09.02.2010

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

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

new A()

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

A* f(){ ...

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

static A globalInstance;
A& f(){ 
  return globalInstance;
}
person Dewfy    schedule 08.02.2010
comment
нет сцены? Что это обозначает? - person nobody; 08.02.2010
comment
Ваш комментарий не имеет смысла. - person Sebastian Mach; 08.02.2010
comment
@Andrew Medico - когда функция возвращает выделенное значение стека, значение должно где-то храниться. В противном случае ваше задание будет потеряно. Вот почему g ++ считает этот случай бессмысленным. В своем посте я объяснил почему. - person Dewfy; 08.02.2010
comment
@Dewfy: g ++ не рассматривает это как бессмысленное, см. Мой пост о том, почему такое поведение разрешено в C ++. Кроме того, мы не знали, что вы подразумеваете под сценой (против смысла). Более того, просто потому, что результат чего-то не используется, не оправдывает действительности. Кроме того, в C ++ вы можете очень хорошо повторно использовать такой оператор, как Foo (). X = 5, например, в (Foo (). x = 5) .member () - person Sebastian Mach; 08.02.2010
comment
Код явно упрощен, но фундаментальный вопрос остается. Если член будет volatile, присваивание является наблюдаемым, и g ++ должен выполнить присваивание - за исключением всего обсуждения rvalue / lvalue. - person MSalters; 08.02.2010
comment
@phresnel (Foo (). x = 5) .member () предполагает только переопределение 'operator =', которое возвращает не int, а ссылку на 'struct A', в этом случае g ++ не будет выдавать предупреждение. - person Dewfy; 08.02.2010
comment
Вы будете смеяться, но (Foo (). X = 5) .member () - вполне допустимый код, для которого g ++ не будет и не должен выдавать диагностику в struct Foo {Foo & operator = (int) {return * this ; } Foo Foo (); статический Foo foo; недействительный член () {}}; Foo Foo :: foo; Foo :: Foo (): x (foo) {} int main () {(Foo (). X = 5) .member (); } - person Sebastian Mach; 08.02.2010
comment
@Dewfy: извините за форматирование, вы увидите лучше, если вставите код внутрь. - person Sebastian Mach; 08.02.2010
comment
@phresnel Я точно рассказал о том, что вы только что написали. Вы должны объявить Foo & operator = (int), чтобы создать смысл этого присваивания - поэтому никаких предупреждений во время компиляции. С другой стороны, очевидно, почему предупреждение появляется для int x - person Dewfy; 08.02.2010
comment
@phresnel: Между прочим, используйте галочки: «здесь идет код», чтобы получить code goes here. Я использовал обратную косую черту, чтобы избежать прежних тиков - person GManNickG; 08.02.2010