16.2.7. Перенаправление
Некоторые функции должны перенаправлять другим функциям один или несколько своих аргументов с неизменными типами. В таких случаях необходимо сохранить всю информацию о перенаправленных аргументах, включая то, является ли тип аргумента константой и является ли аргумент l- или r-значением.
В качестве примера напишем функцию, получающую вызываемое выражение и два дополнительных аргумента. Функция вызовет предоставленное вызываемое выражение с другими двумя аргументами в обратном порядке. Вот первый фрагмент функции обращения:
// шаблон, получающий вызываемое выражение и два параметра
// вызывает предоставленное выражение с "обращенными" параметрами
// flip1 - неполная реализация: спецификатор const верхнего уровня и
// ссылки теряются
template <typename F, typename T1, typename T2>
void flip1(F f, T1 t1, T2 t2) {
f(t2, t1);
}
Этот шаблон работает прекрасно, пока он не используется для вызова функции со ссылочным параметром:
void f(int v1, int &v2) // обратите внимание, v2 - ссылка
{
cout << v1 << " " << ++v2 << endl;
}
Здесь функция f() изменяет значение аргумента, привязанного к параметру v2. Но если происходит вызов функции f() через шаблон flip1, внесенные функцией f() изменения не затронут первоначальный аргумент:
f(42, i); // f() изменяет свой аргумент i
flip1(f, j, 42); // вызов f() через flip1 оставляет j неизменным
Проблема в том, что j передается параметру t1 шаблона flip1. Этот параметр имеет простой, не ссылочный тип int, а не int&. Таким образом, этот вызов создает следующий экземпляр шаблона flip1:
void flip1(void(*fcn)(int, int&), int t1, int t2);
Значение j копируется в t1. Ссылочный параметр в функции f() связан с t1, а не с j.
Определение параметров функции, хранящих информацию типа
Чтобы передать ссылку через функцию, необходимо переписать ее так, чтобы параметры сохраняли принадлежность своих аргументов к l-значениям. Немного поразмыслив, можно предположить, что константность аргументов также необходимо сохранить.
Всю информацию о типе аргумента можно сохранить, определив соответствующий ему параметр функции как ссылку на r-значение параметра типа шаблона. Использование ссылочного параметра (l- или r-значение) позволяет сохранить константность, поскольку спецификатор const в ссылочном типе нижнего уровня. Благодаря сворачиванию ссылок (см. раздел 16.2.5), если определить параметры функции как T1&& и T2&&, можно сохранить принадлежность к l- или r-значениям аргументов функции (см. раздел 16.2.5):
template <typename F, typename T1, typename T2>
void flip2(F f, T1 &&t1, T2 &&t2) {
f(t2, t1);
}
Как и прежде, если происходит вызов flip2(f, j, 42), l-значение j передается параметру t1. Однако в функции flip() для T1 выводится тип int&, а значит, тип t1 сворачивается в int&. Ссылка t1 связана с j. Когда функция flip() вызывает функцию f(), ссылочный параметр v2 в функции f() привязан к t1, который, в свою очередь, привязан к j. Когда функция f() осуществляет инкремент v2, это изменяет значение j.
Параметр функции, являющийся ссылкой на r-значение параметра типа шаблона (т.е. Т&&), сохраняет константность и принадлежность к l- или r-значениям соответствующих ему аргументов.
Эта версия функции flip() решает одну половину проблемы. Она работает прекрасно с функциями, получающими ссылки на l-значение, но неприменима для вызова функций с параметрами ссылок на r-значение. Например:
void g(int &&i, int& j) {
cout << i << " " << j << endl;
}
Если попытаться вызывать функцию g() через функцию flip(), то для параметра ссылки на r-значение функции g() будет передан параметр t2. Даже если функции flip() было передано r-значение, функции g() будет передан параметр, носящий в функции flip() имя t2:
flip2(g, i, 42); // ошибка: нельзя инициализировать int&& из l-значения
Параметр функции, как и любая другая переменная, является выражением l-значения (см. раздел 13.6.1). В результате вызов функции g() в функции flip() передает l-значение параметру ссылки на r-значение функции g().
Использование функции std::forward() для сохранения информации типа в вызове
Чтобы передать функции flip() параметры способом, сохраняющим типы первоначальных аргументов, можно использовать новую библиотечную функцию forward(). Как и функция move(), функция forward() определяется в заголовке utility. В отличие от функции move(), функцию forward() следует вызывать с явным аргументом шаблона (см. раздел 16.2.2). Для этого явного аргумента типа функция forward() возвращает ссылку на r-значение. Таким образом, типом возвращаемого значения функции forward<T> будет Т&&.
Обычно функцию forward() используют для передачи параметра функции, который определен как ссылка на r-значение, параметру типа шаблона. Благодаря сворачиванию ссылок для типа возвращаемого значения функция forward() сохраняет характер (l- или r-значение) переданного ей аргумента:
template <typename Type> intermediary(Type &&arg) {
finalFcn(std::forward<Type>(arg)); // ...
}
Здесь Type используется как тип явного аргумента шаблона функции forward() (выводимый из arg). Поскольку arg — это ссылка на r-значение для параметра типа шаблона, параметр Type представит всю информацию типа в аргументе, переданном параметру arg. Если этот аргумент будет r-значением, то параметр Type будет иметь обычный (не ссылочный) тип и функция forward<Type>() возвратит Type&&. Если аргумент будет l-значением, то (благодаря сворачиванию ссылок) типом параметра Type будет ссылка на l-значение. В данном случае типом возвращаемого значения будет ссылка на r-значение для типа ссылки на l-значение. Снова благодаря сворачиванию ссылок (на сей раз для типа возвращаемого значения) функция forward<Type>() возвратит тип ссылки на l-значение.
При использовании с параметром функции, являющимся ссылкой на r-значение для параметра типа шаблона (Т&&), функция forward() сохраняет все подробности типа аргумента.
Перепишем первоначальную функцию, используя на этот раз функцию forward():
template <typename F, typename T1, typename T2>
void flip(F f, T1 &&t1, T2 &&t2) {
f(std::forward<T2>(t2), std::forward<T1>(t1));
}
Если происходит вызов функции flip(g, i, 42), то параметр i будет передан функции g(), поскольку int& и 42 будут переданы как int&&.
Подобно функции std::move(), для функции std::forward() не стоит предоставлять объявление using. Причина описана в разделе 18.2.3.
Упражнения раздела 16.2.7
Упражнение 16.47. Напишите собственную версию функции обращения и проверьте ее, вызывав функции с параметрами ссылок на r-значение и l-значение.
Более 800 000 книг и аудиокниг! 📚
Получи 2 месяца Литрес Подписки в подарок и наслаждайся неограниченным чтением
ПОЛУЧИТЬ ПОДАРОК