Когда не использовать `auto&&`?
auto&& mytup = std::make_tuple(9,1,"hello");
std::get<0>(mytup) = 42;
cout << std::get<0>(mytup) << endl;
- Есть ли необходимость в копировании / перемещении (без RVO) при возврате из make_tuple?
- Это вызывает неопределенное поведение?
- Я могу оба прочитать и написать универсальную ссылку. Можно
auto&& var = func()
использовать всегда вместоauto var = func()
чтобы не было копирования / перемещения?
4 ответа
Да. Любой возврат из функции, которая не возвращает ссылочный тип, может включать копирование / перемещение. Eliding - это то, о чем RVO. Объект, с которым связана ваша ссылка, нужно как-то инициализировать.
Нет, зачем это? Время жизни временного значения / значения, привязанного к ссылке, определяется областью действия ссылки.
Если func() не возвращает ссылочный тип, не должно быть никакой разницы в эффективности (и поведении)
между
auto&& var = func();
а также
auto var = func();
В обоих случаях создается объект с временем жизни до конца содержащего блока. В одном случае он имеет собственное имя, в другом - через ссылку. В обоих случаях имя может использоваться как lvalue. RVO может одинаково хорошо применяться в любом случае.
Некоторые компиляторы могут оптимизировать для локального объекта лучше, чем для ссылки, даже если в текущем случае ссылка на временный объект на самом деле ничем не отличается от локального объекта.
Если func()
может вернуть ссылку, все будет по-другому - в этом случае вы должны решить, хотите ли вы скопировать / переместить или нет.
Это только когда-либо проблематично в случае, когда инициализатор является вызовом функции, которая возвращает кратковременную ссылку на значение. С меньшим количеством слов и большим количеством кода:
// Fine; lifetime extension applies!
auto&& ref = 42;
auto id = [](int&& i) -> int&& { return std::move(i); };
auto&& uhoh = id(42);
// uhoh is now a stale reference; can't touch it!
По сравнению, auto uhoh = id(42);
работал бы нормально.
В вашем случае, потому что std::make_tuple
возвращает значение, а не ссылку на rvalue, проблем нет.
Я придерживаюсь мнения, что реальная опасность заключается в тех функциях и шаблонах функций со ссылочными параметрами rvalue, которые возвращают ссылку rvalue к тем из некоторых подобъектов, время жизни которых зависит от них. (При этом, что-то так просто, как auto&& ref = std::move(42);
выставляет проблему!)
Ситуация не совсем новая с C++11, рассмотрим: T const& ref = bar(T_factory());
,
В C++ существует специальное правило (даже до C++11), которое гласит, что если вы привязываете ссылку к временному объекту, срок действия временного элемента будет продлен до времени существования ссылки.
Более простой случай:
int foo () {
return 42;
}
int bar () {
const int& x = foo();
return x; // Here is safe to use x
}
Результат оценки звонка make_tuple
является prvalue временным tuple
конкретизации. auto
спецификатор типа будет выведен как тот же tuple
экземпляр (7.1.6.4p6), так mytup
имеет тип tuple<...> &&
, Затем временное значение prvalue продлевается с помощью ссылки mytup
(12.2p5).
- Поскольку временное значение является возвращаемым значением функции, копирование / перемещение не требуется (в RVO все еще может быть
make_tuple
). - Поведение полностью определено.
- Почти во всех случаях
mytup
будет рассматриваться как lvalue, даже если его тип является ссылкой на rvalue. Однако нет смысла использоватьauto &&
поскольку все разумные компиляторы исключат копирование / перемещение из временного.
Чтобы уточнить, о единственном способе mytup
рассматривается как значение, чтобы использовать std::forward<decltype(mytup)>(mytup)
Что и как говорит auto&&?; Однако, если вы знаете тип mytup
(tuple<...>
в этом случае) тогда вы можете также использовать std::move
,