С++: является ли возвращаемое значение L-значением?

Рассмотрим этот код:

struct foo
{
  int a;
};

foo q() { foo f; f.a =4; return f;}

int main()
{
  foo i;
  i.a = 5;
  q() = i;
}

Ни один компилятор не жалуется на это, даже Clang. Почему строка q() = ... правильная?


person John    schedule 24.05.2011    source источник
comment
Вау, хороший вопрос, +1.   -  person Seth Carnegie    schedule 24.05.2011
comment
Как вы думаете, что не так с этим кодом? q() возвращает структуру, а затем вы присваиваете ей значение. Что в этом плохого?   -  person Andy Johnson    schedule 24.05.2011
comment
@Andy: я думаю, что это очень подвержено ошибкам, поскольку присвоение значения возвращаемому значению обычно ничего не делает (за исключением случаев, когда оператор = творит какую-то магию, что, вероятно, является плохой практикой проектирования). Я ожидал, что это будет предупреждение, например, не использовать локальную переменную.   -  person John    schedule 24.05.2011
comment
@Энди Джонсон: широко распространенное заблуждение состоит в том, что люди предполагают, что все, что вы можете использовать в левой части присваивания, должно быть lvalue. В данном случае это требование оказывается нарушенным. Но на самом деле такого требования нет. Встроенное присваивание действительно требует и lvalue в LHS, а перегруженное присваивание — нет. В данном случае мы имеем дело с перегруженным, хотя это и не очевидно.   -  person AnT    schedule 24.05.2011


Ответы (3)


Нет, возвращаемое значение функции является l-значением тогда и только тогда, когда оно является ссылкой (C++03). (5.2.2 [экспр.вызов] / 10)

Если бы возвращаемый тип был базовым типом, это было бы ошибкой компиляции. (5,17 [выраж.зад] / 1)

Причина, по которой это работает, заключается в том, что вам разрешено вызывать функции-члены (даже не-1_ функции-члены) для r-значений типа класса, а назначение foo является функцией-членом, определенной реализацией: foo& foo::operator=(const foo&). Ограничения для операторов в разделе 5 применяются только к встроенным операторам, (5 [expr] / 3), если разрешение перегрузки выбирает вызов перегруженной функции для оператора, тогда применяются ограничения для этого вызова функции. вместо.

Вот почему иногда рекомендуется возвращать объекты типа класса как объекты const (например, const foo q();), однако это может иметь негативные последствия в C++0x, где это может помешать семантике перемещения работать должным образом.

person CB Bailey    schedule 24.05.2011
comment
В качестве конкретного примера рассмотрим std::cout << "Hello, world" << std::endl. Первый operator<< () возвращает lvalue, по которому вы можете вызвать второй operator<< (). Таким образом, эта практика вызова методов для возвращаемых значений более распространена, чем многие думают. - person MSalters; 24.05.2011
comment
И мой мир становится немного больше... Хороший ответ. - person wheaties; 24.05.2011
comment
Это напоминает мне цитату из The Design and Evolution of C++: Это источник идеи, которая с годами превратилась в эмпирическое правило для разработки C++: определяемые пользователем и встроенные типы должны вести себя одинаково по отношению к правила языка и получают такую ​​же степень поддержки со стороны языка и связанных с ним инструментов. Когда идеал был сформулирован, встроенные типы получили наилучшую поддержку, но C++ перевыполнил эту цель, так что теперь встроенные типы получают несколько меньшую поддержку по сравнению с типами, определяемыми пользователем. - person Seth Carnegie; 24.05.2011
comment
@MSalters да, но в случае cout он на самом деле возвращает lvalue (он возвращает себя и по ссылке, *this), а не что-то по значению. Так что это немного другое. - person Seth Carnegie; 24.05.2011
comment
@MSalters: ostream::operator‹‹, как и оператор =, оба возвращают ссылку, которая является L-значением. - person John; 24.05.2011
comment
@MSalters В std::cout << "duh" << std::endl все operator<< возвращают неконстантные ссылки (которые являются lvalues), и поскольку все операторы требуют неконстантных ссылок в качестве своего первого параметра, исходный объект std::ostream не может быть rvalue. В других примерах (и я предполагаю, что это мотивация вопроса) неконстантные функции вызываются временно. - person James Kanze; 24.05.2011
comment
@all: я намеренно написал, что возвращает lvalue, потому что это действительно ссылка. Если бы он не возвращал ссылку, я бы не писал, что он возвращает lvalue. Моя на самом деле точка зрения заключалась в том, что даже эта невинная строка работает, потому что вы можете вызывать методы, включая операторы, в выражениях вызова функций, поэтому q().operator=(i) на самом деле не такое уж исключение. - person MSalters; 26.05.2011
comment
спасибо за указание на разницу = во встроенных типах и типах, определяемых пользователем! - person baye; 28.05.2011

Поскольку структуры могут быть назначены, и ваш q() возвращает копию struct foo, поэтому он присваивает возвращенную структуру предоставленному значению.

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

Это имеет больше смысла (хотя все еще не является «лучшей практикой»)

struct foo
{
  int a;
};

foo* q() { foo *f = new malloc(sizeof(foo)); f->a = 4; return f; }

int main()
{
  foo i;
  i.a = 5;

  //sets the contents of the newly created foo
  //to the contents of your i variable
  (*(q())) = i;
}
person Chad    schedule 24.05.2011
comment
В чем разница между этим и этим: int blah() { int f = 4; return f; } int main() { int a = 99; blah() = a; } есть ли что-то волшебное в структурах? Потому что этот код не компилируется. - person Seth Carnegie; 24.05.2011
comment
@Seth Смотрите ответ Чарльза Бейли - person Chad; 24.05.2011
comment
У Int нет функций-членов, поэтому у них нет и int::operator=(int). Это волшебная часть структур: у них есть методы по умолчанию. - person MSalters; 24.05.2011
comment
@Seth, потому что в вашем примере функция возвращает int, а не объект, поэтому вы не можете ничего ей присвоить. Если бы функция возвращала ссылку на int ( int& ), вы могли бы присвоить ей значение. - person bruno; 24.05.2011
comment
@бруно: s/nothing/anything/ - person Lightness Races in Orbit; 24.05.2011

Одно интересное применение этого:

void f(const std::string& x);
std::string g() { return "<tag>"; }

...

f(g() += "</tag>");

Здесь g() += изменяет временное, что может быть быстрее, чем создание дополнительного временного с помощью +, потому что куча, выделенная для возвращаемого значения g(), может уже иметь достаточную свободную емкость для размещения </tag>.

Посмотрите, как он работает на ideone.com с GCC/C++11.

Итак, кто из новичков в вычислительной технике сказал что-то об оптимизации и зле...? ;-].

person Tony Delroy    schedule 25.05.2011