69 / 57 / 14
Регистрация: 20.12.2013
Сообщений: 656
1

Передача параметров через rvalue

08.01.2018, 00:35. Показов 2931. Ответов 8
Метки нет (Все метки)

Author24 — интернет-сервис помощи студентам
C++
1
2
3
4
5
6
7
8
9
10
11
12
13
template<class T> T&& fuc(T&& t)
{
    return t;
};
 
int main() {
 
    double v1 = 0.9;
    const double v2 = 0.9;
    fuc(v1);
    fuc(v2);
    fuc(67);
};
Почему так не работает ("невозможно преобразовать int в &&int" при вызове fuc(67))?
Работает при варианте
C++
1
2
3
4
template<class T> T fuc(T&& t)
{
    return t;
};
Но ведь 67 - rvalue, т.е. в обоих случаях должно быть T&& и в параметре функции, и в возвращаемом значении после разрешения параметра шаблона?
0
Лучшие ответы (1)
Programming
Эксперт
94731 / 64177 / 26122
Регистрация: 12.04.2006
Сообщений: 116,782
08.01.2018, 00:35
Ответы с готовыми решениями:

Rvalue передача в функцию
Снова я с тупыми вопросами по поводу rvalue. Я не могу понять, почему такой код не работает: ...

Ссылка на rvalue, является ли она сама rvalue?
А верно ли, что ссылка на rvalue сама не является rvalue? Вот такой код является валидным: #include...

C++ expressions - rvalue, glvalue, prvalue, xvalue, lvalue, а также rvalue reference: что есть что?
Доброго времени суток, не понимаю до конца деление С++ - выражений (приложение 1). Lvalue вроде...

Передача параметров
Добрый день! Столкнулся со следующей ситуацией. При передаче 2-х мерного статического массива по...

8
277 / 226 / 93
Регистрация: 27.06.2016
Сообщений: 639
08.01.2018, 00:46 2
AndrSlav, T&& - это не rvalue ссылка, а универсальная ссылка. Это в том случае, если T - выводимый по вызову функции тип.
0
69 / 57 / 14
Регистрация: 20.12.2013
Сообщений: 656
08.01.2018, 01:02  [ТС] 3
Цитата Сообщение от alex white Посмотреть сообщение
T&& - это не rvalue ссылка, а универсальная ссылка. Это в том случае, если T - выводимый по вызову функции тип.
Да, я как раз и пытаюсь понять - мутная какая-то тема.
Как понял,при вызове fuc(v1) v1 трактуется как lvalue, т.е. T&. Тогда будет инстанцирован шаблон T& fuc(T& && t), что равно T& fuc(T& t). А при вызове fuc(67) инстанцируется T&& fuc(T&& && t), что равно T&& fuc(T&& t). Но ведь если выходной параметр T&&, то должно быть то же самое (T&& && равно T&&)?
0
277 / 226 / 93
Регистрация: 27.06.2016
Сообщений: 639
08.01.2018, 01:07 4
AndrSlav, если мы даже просто вернёмя к rvalue-ссылкам:
C++
1
2
3
4
vector<int> &&rv(vector<int> &&r)
{
    return r;
}
Этот код не будет работать. Вряд ли смогу "умными" словами объяснить, многое позабыл, но если мы держим ссылку vector<int> &&, она не передаётся дальше как vector<int> &&, пока мы не применим к ней std::move. Это логично, так как нам часто нужно с ней несколько действий выполнить и при этом иметь гарантии, что объект раньше времени не переместят. В общем, можно сказать наверное, что получив rvalue ссылку, мы держим её как lvalue ссылку.
Так работает:
C++
1
2
3
4
vector<int> &&rv(vector<int> &&r)
{
    return std::move(r);
}
Ну и рабочий вариант для универсальных ссылок:
C++
1
2
3
4
template<class T>T&& ur(T&& x)
{
    return std::forward<T>(x);
}
Добавлено через 3 минуты
AndrSlav,
Но конечно, если возвращаете ссылку, не присваивайте результат другой ссылке, она может быть висячей.
1
16 / 17 / 3
Регистрация: 23.09.2017
Сообщений: 39
08.01.2018, 01:13 5
Цитата Сообщение от alex white Посмотреть сообщение
а универсальная ссылка.
Цитата Сообщение от alex white Посмотреть сообщение
универсальных ссылок
Выбросьте эту чушню из головы уже. Один дядя один раз придумал и понеслось...

Цитата Сообщение от alex white Посмотреть сообщение
В общем, можно сказать наверное, что получив rvalue ссылку, мы держим её как lvalue ссылку.
Всё проще. Сама rvalue-ссылка - это именованная переменная, так что выражение r будет иметь lvalue категорию. std::move возвращает rvalue-reference и это выражение будет иметь категорию xvalue. А результат std::forward будет зависеть от типа T.
1
69 / 57 / 14
Регистрация: 20.12.2013
Сообщений: 656
08.01.2018, 01:54  [ТС] 6
Цитата Сообщение от LimeBush Посмотреть сообщение
Всё проще. Сама rvalue-ссылка - это именованная переменная, так что выражение r будет иметь lvalue категорию. std::move возвращает rvalue-reference и это выражение будет xvalue.
Но вот так тоже ошибки не выбрасывает.
C++
1
2
3
4
std::vector<int> rv(std::vector<int> &&r)
{
    return r;
}
Вроде как здесь даже более понятно - перемещаем внешнюю ппеременную в функцию, а при возврате этой локальной переменной вызывается конструктор перемещения для вектора. Кошмарная тема.

p.s. Я чушь написал - конечно, в одном случае ссылка,в другом переменная

Добавлено через 29 минут
Я правильно понимаю,что раньше наиболее универсальным параметром функции были const&, но они не позволяли перемещать rvalue (а изменять по ссылке и так не очень принято). А && более современный вариант, позволяющий перемещение, а также изменение объекта по lvalue ссылке?
0
16 / 17 / 3
Регистрация: 23.09.2017
Сообщений: 39
08.01.2018, 02:09 7
Цитата Сообщение от AndrSlav Посмотреть сообщение
а при возврате этой локальной переменной вызывается конструктор перемещения для вектора.
Нет, будет копирование вектора в возвращаемое значение.
Цитата Сообщение от AndrSlav Посмотреть сообщение
но они не позволяли перемещать rvalue
Да, в функции было не понятно, нужен ли еще переданный объект вызывающей стороне, или можно загрести его ресурсы себе. Обходилось с помощью костылей.
Цитата Сообщение от AndrSlav Посмотреть сообщение
позволяющий перемещение, а также изменение объекта по lvalue ссылке?
Я не понял вопроса.
1
69 / 57 / 14
Регистрация: 20.12.2013
Сообщений: 656
08.01.2018, 08:07  [ТС] 8
Допустим, есть шаблон
C++
1
template<class T> fun(T val);
Инстанциация происходит при вызове. Но вот при вызове с double, к примеру, возможны варианты с передачей lvalue, rvalue. Так что, появляется функция
C++
1
fun(double val);
или набор
C++
1
2
fun(double& val);
fun(double&& val);
?
Или как-то по-другому?
0
16 / 17 / 3
Регистрация: 23.09.2017
Сообщений: 39
08.01.2018, 15:18 9
Лучший ответ Сообщение было отмечено AndrSlav как решение

Решение

Цитата Сообщение от AndrSlav Посмотреть сообщение
Так что, появляется функция
Да, если был вывод типа из аргумента.
Цитата Сообщение от AndrSlav Посмотреть сообщение
Или как-то по-другому?
Разберем два правила, работающих в контексте вывода типа.
Правило первое.
При выводе типа (в шаблоне или для auto) ссылочность отбрасывается, т.е., если тип будет выводится из аргумента, то тип будет не ссылочным, за исключением случаев, когда тип выводится совместно с использованием rvalue-ссылки, но об этом чуть позже.
Если нужна ссылочность, то при таком шаблоне нужно указывать тип при использовании:
C++
1
2
3
4
5
6
7
8
9
template<class T> void fun(T val) {}
//...
double x{};
fun(x);//T = double
fun(std::move(x));//T = double
fun(33.6);//T = double
fun<double&>(x);//T = double&
fun<double&&>(std::move(x));//T = double&&
fun<double&&>(33.8);//T = double&&
Правило второе.
Ссылки умеют сворачиваться (схлопываться) при выводе типа. Это значит, что если при выводе типа получается ссылка на ссылку, то она сворачивается до ссылки. Правила сворачивания приведу как табличку истинности:
# & &&
& & &
&& & &&
т.е. ссылки схлопываются до rvalue-ссылки только тогда, когда получается rvalue-ссылка на rvalue-ссылку.
Для примера:
C++
1
2
3
4
5
6
    typedef double& LVR;//lvalue-reference
    typedef LVR& LLVR;//lvalue-reference + lvalue-reference = lvalue-reference
    typedef LVR&& RLVR;//lvalue-reference + rvalue-reference = lvalue-reference
    typedef double&& RVR;//rvalue-reference
    typedef RVR& LRVR;//rvalue-reference + lvalue-reference = lvalue-reference
    typedef RVR&& RRVR;//rvalue-reference + rvalue-reference = rvalue-reference
Эти два правила при выводе типа позволяют организовать аргументы, которые могут ссылаться на всё что угодно.
Разберем пример из начального поста:
C++
1
2
3
4
5
6
7
8
9
10
11
12
13
template<class T> T&& fuc(T&& t)
{
    return t;
};
 
int main() {
 
    double v1 = 0.9;
    const double v2 = 0.9;
    fuc(v1);//1
    fuc(v2);//2
    fuc(67);//3
};
Ситуация первая fuc(v1):
v1 у нас имеет тип double.
в параметрах fun принимает агрумент типа T&& (при этом T выводится из аргумента). Выражение v1 будет lvalue, значит v1 не может быть привязан к double&&. Однако, если вывести T не как double, а как double&, то мы получаем ситуацию double& &&, а значит ссылка будет схлопнута до double&. Итого получаем, что тип T нужно вывести именно как lvalue-ссылку double& для успеха. В возвращаемом значении у нас T&&, а значит при выведенном T как double& мы получаем double& &&, которое также схлопнется до double&. Получаем функцию
C++
1
2
3
4
double& fun(double& r)
{
   return r;
}
думаю, здесь очевидно, что всё хорошо.
Во втором случае fuc(v2), v2 имеет тип const double, так что всё будет также, как и с v1, только еще к типам добавится const:
C++
1
2
3
4
const double& fun(const double& r)
{
   return r;
}
В третьем случае fuc(67) 67 - даст временный объект rvalue. Оно может быть привязано к не константной rvalue-ссылке, а значит T будет выведен как double. Считай, просто вместо T подставили double для успеха:
C++
1
2
3
4
double&& fun(double&& r)
{
   return r;//ошибка
}
О том, почему произошла ошибка в функции я уже писал, но еще раз. Сама переменная r - это rvalue-reference, но она - именованная переменная, поэтому она будет участвовать в выражении как lvalue. Как известно, lvalue неявно к rvalue-reference не преобразуется, поэтому и ошибка. Нужен std::forward.
std::forward в стандарте - это
C++
1
2
3
template <class T> constexpr T&& forward(typename remove_reference<T>::type& t) noexcept;
template <class T> constexpr T&& forward(typename remove_reference<T>::type&& t) noexcept;
//Returns: static_cast<T&&>(t).
Для простоты отбросим constexpr и сделаем свой простой forward:
C++
1
2
3
4
5
6
7
8
9
10
11
template <class T>
T&& forward(std::remove_reference_t<T>& t) noexcept
{
   return static_cast<T&&>(t);
}
 
template <class T>
T&& forward(std::remove_reference_t<T>&& t) noexcept
{
   return static_cast<T&&>(t);
}
И сопоставим это с нашей функцией:
C++
1
2
3
4
template<class T> T&& fuc(T&& t)
{
    return forward<T>(t);//Добавили std::forward - всё ок. Почему?
};
Предположим, что мы вызвали fuc с нашим v1 (lvalue), тогда получаем, что T вывелся как double&, а значит t - это lvalue-reference.
Теперь рассмотрим работу forward<T>. Мы инстанцируем его с типом T (double&).
Значит получаем такое:
C++
1
2
3
4
double& && forward(std::remove_reference_t<double&>& t) noexcept
{
   return static_cast<double& &&>(t);
}
remove-reference_t уберет ссылку, а в static_cast и в возвращаемом значении ссылки схлопнуться:
C++
1
2
3
4
double& forward(double& t) noexcept
{
   return static_cast<double&>(t);
}
итого, std::forward даст double&.
А теперь передадим в fuc rvalue - std::move(v1).
Получаем, что тип T в fuc выведется как double, а значит мы инстанцируем forward с типом double (forward<double>(r)), при этому передаем в качестве аргумента функции rvalue-reference, а значит будет использована вторая версия forward:
C++
1
2
3
4
double&& forward(std::remove_reference_t<double>&& t) noexcept
{
   return static_cast<double&&>(t);
}
То есть в таком случае forward уже вернет double&&, а не double&. На этом вся магия кончается. То есть, как видим, никакой магии и никаких магических универсальных ссылок здесь нет. Пара простых правил и зелье готово. Надеюсь, стало чуточку понятнее. Просто попрактикуйтесь, и всё станет ясно.
1
08.01.2018, 15:18
IT_Exp
Эксперт
87844 / 49110 / 22898
Регистрация: 17.06.2006
Сообщений: 92,604
08.01.2018, 15:18
Помогаю со студенческими работами здесь

Передача параметров в функцию
Люди помогите, пожалуйста. Есть такая простая задачка: В одномерном массиве, состоящем из n...

Передача параметров функции
доброго времени суток:) есть программа реализующая метод градиентного спуска. но да это неважно ...

Передача параметров в функцию
Помогите пожалуйста сделать лабораторную по ЯП. // Лабораторная работа 2 // тема &quot;Передача...

Передача параметров, оператор <<
Я вот решил написать простенькую программку на свеже скачанной среде. Программка создает функцию,...


Искать еще темы с ответами

Или воспользуйтесь поиском по форуму:
9
Ответ Создать тему
Опции темы

КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin
Copyright ©2000 - 2024, CyberForum.ru