Непонимание двойной пересылки lvalue - когда передается по значению
#include <iostream>
#include <vector>
#include <type_traits>
#include <utility>
using namespace std;
template <typename Func, typename... Args>
void proxy(Func f, Args&&... args) {
f(std::forward<Args>(args)...);
}
void real_func(vector<int> v) {
cout << "size: " << v.size() << endl;
}
void multicast_func(vector<int> v) {
proxy(real_func, std::forward<vector<int>>(v));
proxy(real_func, std::forward<vector<int>>(v));
}
int main()
{
vector<int> ints = {1, 2, 3};
multicast_func(ints);
return 0;
}
и вывод:
size: 3
size: 0
почему не 3, 3? в какой момент это lvalue становится rvalue и удаляется?
5 ответов
std::forward
предназначен для использования с универсальными ссылками.
Параметр multicast_func
не является универсальной ссылкой, поэтому std::forward
не имеет смысла:
void multicast_func(vector<int> v) {
proxy(real_func, std::forward<vector<int>>(v));
proxy(real_func, std::forward<vector<int>>(v));
}
В этом случае он эффективно действует как std::move
(потому что параметр шаблона не является ссылкой (lvalue)).
Прототип для std::forward
Вызов в вашем коде:
template< class T >
constexpr T&& forward( typename std::remove_reference<T>::type& t ) noexcept;
Когда вызывается с не ссылочным типом, он фактически делает ссылку на rvalue из аргумента, которая затем перемещается. std::vector
гарантированно будет пустым после перемещения из него, поэтому size
становится 0.
std::forward
Если не указан ссылочный тип, приведенный объект будет приведен к значению. Это означает, что первый звонок
proxy(real_func, std::forward<vector<int>>(v));
сделаю v
значение, которое означает, что оно переместит его в real_func
, Затем второй вызов использует, что перемещено из объекта, и вы получите размер 0
так как он был опустошен.
Это имеет смысл, если мы посмотрим на функцию. Версия std::forward
ты звонишь
template< class T >
constexpr T&& forward( typename std::remove_reference<T>::type& t ) noexcept;
С тех пор как вы прошли std::vector<int>
за T
это означает, что он вернет std::vector<int>&&
, Итак, хотя v
является lvalue, оно преобразуется в rvalue. Если вы хотите сохранить значение v
тогда вам нужно использовать std::vector<int>&
, Это дает вам std::vector<int>& &&
и ссылочные правила Colapse превращает это в std::vector<int>&
оставляя вас с lvalue.
в какой момент это lvalue становится rvalue и удаляется?
В 1-й раз proxy
вызывается v
преобразуется в r-значение, а затем перемещается из real_func
,
void multicast_func(vector<int> v) {
// the return type of std::forward is T&&, i.e. vector<int>&& here
// for functions whose return type is rvalue reference to objec, the return value is an rvalue
// that means v is converted to an rvalue and passed to proxy
proxy(real_func, std::forward<vector<int>>(v));
// v has been moved when passed to real_func as argument
proxy(real_func, std::forward<vector<int>>(v));
}
Использование в proxy
это общее использование std::forward
; в соответствии с аргументом является lvalue или rvalue, параметр шаблона будет выведен как T&
или же T
, За T&
std::forward
вернет lvalue, для T
std::forward
вернет значение r, поэтому категория значений сохраняется. При указании аргумента шаблона только такая емкость теряется.
В первом звонке proxy
параметр vector<int> v
(в функции real_func
) является движением, построенным из значения, поэтому v
(в функции multicast_func
) был пуст.
Но если вы измените тип paremeter как cosnt vector<int> &
результат будет 3, 3. Поскольку constrctor перемещения не был вызван.
void real_func(const vector<int>& v) {
cout << "size: " << v.size() << endl;// the output is 3, 3
}