С другой стороны, когда происходит вызов функции f3()
для l-значения i
, типом T
будет int&
. Когда определяется и инициализируется локальная переменная t
, у нее будет тип int&
. Инициализация переменной t
свяжет ее с параметром val
. При присвоении переменной t
одновременно изменяется и параметр val
. В этом экземпляре функции f3()
оператор if
всегда будет возвращать значение true
.
На удивление сложно написать правильный код, когда задействованные типы могут быть простыми (не ссылочными) типами или ссылочными типами (хотя такие классы трансформации типов, как remove_reference
(см. раздел 16.2.3), вполне могут помочь в этом).
На практике параметры в виде ссылки на r-значение используются в одном из двух случаев: либо когда шаблон перенаправляет свои аргументы, ли когда шаблон перегружается. Перенаправление рассматривается в разделе 16.2.7, а перегрузка шаблона в разделе 16.3, а пока достаточно знать, что стоит обратить внимание на то, что шаблоны функций, использующие ссылки на r-значение, зачастую используют перегрузку таким же образом, как описано в разделе 13.6.3:
template
//
template
//
Подобно нешаблонным функциям, первая версия будет связана с изменяемым r-значением, а вторая с l-значением или константным r-значением.
Упражнение 16.42. Определите типы Т
и val
в каждом из следующих вызовов:
template
int i = 0; const int ci = i;
(a) g(i); (b) g(ci); (c) g(i * ci);
Упражнение 16.43. Используя определенную в предыдущем упражнении функцию, укажите, каким будет параметр шаблона g()
при вызове g(i = ci)
?
Упражнение 16.44. Используя те же три вызова, что и в первом упражнении, определите типы T
, если параметр функции g()
объявляется как T
(а не Т&&
) и как const Т&
?
Упражнение 16.45. С учетом следующего шаблона объясните происходящее при вызове функции g()
с таким литеральным значением, как 42, и с переменной типа int
?
template
std::move()
Библиотечная функция move()
(см. раздел 13.6.1) — хороший пример шаблона, использующего ссылки на r-значение. К счастью, функцию move()
можно использовать, не понимая механизма работы используемого ею шаблона. Однако изучение работы функции move()
может помочь понять и использовать шаблоны.
В разделе 13.6.2 обращалось внимание на то, что, хотя и нельзя непосредственно привязать ссылку на r-значение к l-значению, функцию move()
можно использовать для получения ссылки на r-значение, связанной с l-значением. Поскольку функция move()
может получать аргументы, по существу, любого типа, нет ничего удивительного в том, что move()
— это шаблон функции.
std::move()
Стандартное определение функции move()
таково:
//
//
//
template
typename remove_reference
//
return static_cast
}
Этот код короток, но сложен. В первую очередь, параметр функции move()
, Т&&
является ссылкой на r-значение типа параметра шаблона. Благодаря сворачиванию ссылок этот параметр может соответствовать аргументу любого типа. В частности, функции move()
можно передать либо l-, либо r-значение:
string s1("hi!"), s2;
s2 = std::move(string("bye!")); //
s2 = std::move(s1); //
//
std::move()
В первом присвоении аргумент функции move()
является r-значением, полученным в результате выполнения конструктора string("bye")
класса string
. Как уже упоминалось, при передаче r-значения ссылочному r-значению параметра функции выведенный из этого аргумента тип является ссылочным типом (см. раздел 16.2.5). Таким образом, в вызове std::move(string("bye!"))
:
• выведенным типом T
будет string
;
• следовательно, экземпляр шаблона remove_reference
создается с типом string
;