Непонимание двойной пересылки 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, для Tstd::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
}
Другие вопросы по тегам