Если не использовать `auto &&`?
auto&& mytup = std::make_tuple(9,1,"hello");
std::get<0>(mytup) = 42;
cout << std::get<0>(mytup) << endl;
- Имеется ли копия/перемещение (без RVO) при возврате из
make_tuple?
- Это вызывает поведение undefined?
- Я могу читать как запись универсальной ссылки. Может ли
auto&& var =
func()
использоваться всегда вместо auto var = func()
, чтобы не было копирования/перемещения?
Ответы
Ответ 1
-
Да. Любое возвращение из функции, которая не возвращает ссылочный тип, может включать копирование/перемещение. Элидинг - вот что такое RVO. Объект, с которым связана ваша ссылка, должен быть каким-то образом инициализирован.
-
Нет. почему? Время жизни временной /prvalue, привязанное к ссылке, определяется объемом ссылки.
-
Если func() не возвращает ссылочный тип, не должно быть никакой разницы в эффективности (или поведении)
между
auto&& var = func();
и
auto var = func();
В обоих случаях создается объект с продолжительностью жизни до конца содержащего блока. В одном случае оно имеет свое имя, в другом - по ссылке. В обоих случаях имя может использоваться как lvalue. RVO можно одинаково хорошо применять в любом случае.
Некоторые компиляторы могут лучше оптимизироваться для локального объекта, чем для ссылки, даже если в текущем случае ссылка на временное не отличается от локального объекта.
Если func()
может вернуть ссылку, все будет по-другому - в этом случае вы должны решить, хотите ли вы копировать/перемещать или нет.
Ответ 2
Это всегда проблематично в том случае, когда инициализатор является вызовом функции, который возвращает короткоживущую ссылку rvalue. С меньшим количеством слов и более кода:
// 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);
проявляет проблему!)
Ситуация не совсем новая для С++ 11, рассмотрим: T const& ref = bar(T_factory());
.
Ответ 3
Результатом оценки вызова make_tuple
является временное значение prvalue для экземпляра tuple
. Спецификатор типа auto
будет выведен как один и тот же экземпляр tuple
(7.1.6.4p6), поэтому mytup
имеет тип tuple<...> &&
. Временное значение prvalue затем продлевается продолжительностью жизни с помощью ссылки mytup
(12.2p5).
- Поскольку временным является возвращаемое значение функции, нет задействованного копирования/перемещения (по-прежнему может быть RVO внутри
make_tuple
).
- Поведение полностью определено.
- Для почти всех случаев
mytup
будет рассматриваться как lvalue, даже если его тип является ссылкой rvalue. Однако нет смысла использовать auto &&
, поскольку все разумные компиляторы будут исключать копию/перемещение из временного.
Чтобы прояснить, единственный способ иметь mytup
, рассматриваемый как rvalue, - это использовать std::forward<decltype(mytup)>(mytup)
, как в Что делает auto && скажите нам?; однако, если вы знаете тип mytup
(tuple<...>
в этом случае), вы также можете использовать std::move
.
Ответ 4
В С++ существует определенное правило (даже до С++ 11), в котором говорится, что если вы привязываете ссылку на временное, время жизни временного объекта будет расширено до времени жизни ссылки.
Простейший случай:
int foo () {
return 42;
}
int bar () {
const int& x = foo();
return x; // Here is safe to use x
}