Почему требования RVO настолько ограничительны?

Еще одна std::move почему std::move предотвращает (неназванный) возврат-значение-оптимизацию? " вопрос:

Почему std :: move предотвращает RVO? объясняет, что стандарт специально требует, чтобы объявленный тип возвращаемой функции должен соответствовать типу выражения в операторе return. Это объясняет поведение соответствующих компиляторов; однако это не объясняет обоснования ограничения.

Почему правила для RVO не делают исключение для случая, когда возвращаемый тип функции - T а тип return выражения - T&&?

Я также осознаю, что реализация таких вещей в компиляторах не предоставляется бесплатно. Я предлагаю только исключить такое исключение, но не обязательно.

Я также знаю, что return std::move(...) не требуется, поскольку С++ 11 уже требует, чтобы семантика перемещения использовалась, когда RVO не может быть применено. Тем не менее, почему бы не переносить явный запрос на оптимизацию, а не превращать его в пессимизацию?


(Помимо этого: почему return-value-optimization и rvo не являются синонимами?)

Ответ 1

auto foo() -> T&&;

auto test() -> T
{
    return foo();
}

Вы говорите, что в этом случае RVO должно быть разрешено применять. Однако рассмотрите эту юридическую реализацию foo:

T val;

auto foo() -> T&&
{
   return static_cast<T&&>(val); // because yes, it legal
}

Мораль: только с провалами, которые вы знаете наверняка, у вас есть временное и самое важное, вы знаете точное время жизни временного, чтобы вы могли избежать его строительства и разрушения. Но с xvalues (например, возврат T&&) вы не знаете, действительно ли это связано с временным, вы не знаете, когда это значение было создано и когда оно выходит из сферы действия, или даже если вы знаете, что не можете его изменить строительство и разрушение, как показано выше.

Я не уверен, что полностью понимаю. Если RVO разрешено применять для test(), почему это было бы хуже, чем в случае теста: T temp = foo(); return temp; T temp = foo(); return temp; что позволило бы NRVO?

Это не то, что это хуже. Это просто невозможно. С вашим примером temp является локальной переменной в функции, где вы хотите применить NRVO, т.е. test. Таким образом, это объект, полностью "известный" в контексте test, известно его время жизни, известна нормальная точка ctor и dtor. Поэтому вместо того, чтобы создавать temp переменную в кадре стека test она создается в кадре стека вызывающего. Это означает, что нет копии объекта из фрейма стека test в стек стека вызывающего. Также, пожалуйста, посмотрите, что в этом примере foo() совершенно не имеет значения. Это могло быть что угодно при инициализации temp:

auto test() -> T
{
    T temp = /*whatever*/;

    return temp; // NRVO allowed
}

Но с return foo() вы не можете удалить копию просто потому, что не можете знать, к какому объекту привязывается ссылка возврата. Это может быть ссылка на любой объект.