Является ли членом структуры rval значение 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 в С++?
Теперь проблема в том, что я искал стандарт С++ 98/03, ничего не узнавая о том, является ли fun().v
значением lvalue или rvalue.
Итак, что это?
Ответы
Ответ 1
Член выражения 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 является lvalue.
Ответ 2
Изменить: Хорошо, я догадываюсь, что у меня есть что-то из стандарта:
Обратите внимание, что 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 - это разные типы.
Ответ 3
Я заметил, что gcc имеет очень мало компромиссов относительно использования rvalues как lvalues в выражениях присваивания. Это, например, компилируется просто отлично:
class A {
};
extern A f();
void g()
{
A myA;
f() = myA;
}
Почему это юридическое, а это нет (т.е. он не компилируется), хотя меня действительно смущает:
extern int f();
void g()
{
f() = 5;
}
ИМХО, у стандартного комитета есть некоторые объяснения относительно lvalues, rvalues и где они могут быть использованы. Это одна из причин, почему меня так интересует этот вопрос о rvalues .
Ответ 4
Это становится очевидным, если вы считаете, что компилятор будет генерировать конструктор по умолчанию, конструктор копии по умолчанию и стандартный оператор присваивания копии для вас, если ваш 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() возвращать a 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
}
Вы можете определить время жизни временного объекта в текущем выражении. И тогда вы также можете изменить переменные-члены; если бы вы не могли, то вероятность того, что вы сможете вызвать методы, не являющиеся константами, будет вестись ad absurdum.
edit: И вот требуемые цитаты:
12.2 Временные объекты:
- 3) [...] Временные объекты уничтожаются как последний шаг при оценке полного выражения (1.9), который (лексически) содержит точку, в которой они были созданы. [...]
а затем (или лучше, раньше)
3.10 Lvalues и rvalues:
- 10) Для изменения объекта требуется lvalue для объекта, за исключением того, что rvalue типа класса также может использоваться для изменения его референта при определенных обстоятельствах. [Пример: функция-член, вызываемая для объекта (9.3), может изменять объект. ]
И пример использования: http://en.wikibooks.org/wiki/More_C%2B%2B_Idioms/Named_Parameter
Ответ 5
Это хорошее время, чтобы узнать, что xvalues glvalues .
Rvalues может быть двух типов - prvalues и xvalues . Согласно новому стандарту С++ 17
A prvalue - выражение, чья оценка инициализирует объект, бит-поле или операнд оператора, как определено контекстом, в котором он появляется.
поэтому что-то вроде fun()
в вашем примере оценивает значение prvalue (которое является rvalue). Это также говорит о том, что fun().v
не является prvalue, так как это не инициализация ванили.
Xvalues , которые также являются значениями r, определены так:
xvalue (значение "eXpiring" ) также относится к объекту, обычно ближе к концу его жизненного цикла (так что его ресурсы могут быть перемещены, например). Определенные типы выражений с использованием ссылок rvalue (8.3.2) дают значения x. [Пример: результат вызова функции, тип возврата которой является ссылкой rvalue на тип объекта, представляет собой значение xvalue (5.2.2). - конец примера]
В дополнение к rvalues другая категория значений зонтика - это glvalue, которая имеет два типа xvalues и традиционные lvalues .
В этой точке мы определили основные категории значений. Это можно визуализировать так:
![введите описание изображения здесь]()
В категории glvalue можно в целом понимать, что означали lvalues , прежде чем перемещение семантики стало вещью - вещь, которая может быть в левой части выражение. glvalue означает обобщенное значение lvalue.
Если мы посмотрим на определение xvalue, то он говорит, что что-то есть xvalue, если оно близко к концу его жизни. В вашем примере fun().v
находится ближе к концу своего срока службы. Таким образом, его ресурсы могут быть перемещены. И поскольку его ресурсы можно перемещать, это не значение lvalue, поэтому ваше выражение подходит только к единственной категории значений листьев - xvalue.
Ответ 6
У вас нет сцены. Возвращенная структура распределяется по стеку, поэтому результат присваивания немедленно теряется.
Ваша функция должна eiter выделять новый экземпляр A по:
new A()
В этом случае лучшая подпись
A* f(){ ...
Или вернуть существующий экземпляр, например:
static A globalInstance;
A& f(){
return globalInstance;
}