В этой функции foo
является lvalue типа "rvalue reference to Foo
". Когда вы возвращаете его, так как копия должна быть построена (в связи с типом возвращаемого значения), вы строите совершенно новое значение, которое делает bar(...)
prvalue, согласно §3.10.1.5:
Prvalue ("pure" rvalue) является значением r, которое не является значением x. [Пример: результат вызова функции, тип возврата которой не является ссылкой, является значением prvalue. Значение литерала, такого как 12, 7.3e5 или true, также является значением prvalue. - конец пример]
В связи с тем, что внутри функции, foo
является именующее, выражение return foo
не является кандидатом на строительство переезда и the copy constructor is selected.
И да, здесь применяется RVO, предполагая, что движение не выбрано первым. В этом нет ничего особенного. В соответствии с §12.8.31:
Этот элизии от копирования/перемещения операции, называемой копией элизии, допускается в следующих случаях (которые могут быть объединены, чтобы исключить несколько копий):
- в return в функции с типом возвращаемого класса, когда выражение является именем энергонезависимого автоматического объекта (кроме функции или параметра catch-clause) с тем же самым cv-неквалифицированным типом, что и возвращаемый тип функции, копия/перемещение можно опустить, построив автоматический объект непосредственно в возвращаемое значение функции
[...]
Чтобы уточнить, foo
сами по себе именующему, но заявление:
return foo;
в конечном счете результаты (от выражения bar(...)
) в prvalue из-за того, что при условии, что возврат тип, выражение равнозначно:
return Foo(foo);
, что означает, что временное значение, скопированное из foo
возвращается из функции bar
.
Перечитывая ответ, он по-прежнему не имеет смысла для меня. Вы говорите, из-за того, что внутри функции foo является lvalue, выражение return foo не является кандидатом на конструкцию перемещения и выбран конструктор копирования. Почему это верно в одном случае, а не в другом?
При возврате foo
вы должны создать новое значение Foo
(потому что вы возвращаете копию) из Lvalue ссылки foo
. Это делается неявно конструктором копирования. Таким образом, return foo;
эквивалентен return Foo(foo)
. Учитывая, что foo
является lvalue, выбирается конструктор копирования (а не конструктор перемещения).
Теперь, когда у вас есть это новое временное значение (построенное из foo
), само значение, которое выходит из выражения bar(...)
, является prvalue. Так что, когда вы делаете:
auto res = bar(...);
Вы должны построить Foo
копию из prvalue. Поскольку prvalue также является rvalue, выбирается конструктор с ссылочным параметром rvalue (move constructor).
Возможный дубликат [Какие выражения создают значения xvalues?] (Http://stackoverflow.com/questions/11581903/what-expressions-create-xvalues) –
Я не думаю, что это дубликат, так как я спрашиваю о очень частный случай – ricab
Выбранный ответ начинается следующим образом: «Выражение представляет собой значение xvalue, если оно: • результат вызова функции, неявно или явно, тип возвращаемого значения которой является ссылкой на тип объекта». Функция возвращает значение, а не ссылку или значение rvalue. Таким образом, ваша функция не возвращает значение xvalue, и на ваш вопрос будет дан ответ. Просить о очень конкретном случае, который уже рассмотрен в комплексном ответе, кажется, все еще считается дубликатом для меня. –