Проблема в том, что, глядя на тип параметра функции func()
, невозможно определить уникальный тип для аргумента шаблона. Вызов функции func()
мог бы создать экземпляр версии функции compare()
, получающей целые числа или версию, получающую строки. Поскольку невозможно идентифицировать уникальный экземпляр для аргумента функции func()
, этот вызов не будет откомпилирован.Неоднозначность вызова функции func()
можно устранить при помощи явных аргументов шаблона:// ok: явно определенная версия экземпляра compare()
func(compare); // передача compare(const int&, const int&)
Это выражение вызывает версию функции func()
, получающую указатель на функцию с двумя параметрами типа const int&
.Когда возвращается адрес экземпляра шаблона функции, контекст должен позволять однозначно идентифицировать тип или значение для каждого параметра шаблона.
16.2.5. Дедукция аргумента шаблона и ссылки
Чтобы лучше понять дедукцию типа, рассмотрим такой вызов функции где параметр функции p
является ссылкой на параметр типа шаблона T
:template void f(Т &p);
Обратите внимание на два момента: здесь применяются обычные правила привязки ссылок; и спецификаторы const
здесь нижнего уровня, а не верхнего.Дедукция типа из параметров ссылки на l-значения функцийКогда параметр функции представляет собой обычную ссылку (l-значение) на параметр типа шаблона (т.е. имеющего форму T&
), правила привязки гласят, что передавать можно только l-значения (например, переменная или выражение, возвращающее ссылочный тип). Этот аргумент может быть или не быть константным. Если аргумент будет константой, то тип Т
будет выведен как константный:template void f1(Т&); // аргумент должен быть l-значением
// вызовы f1() используют ссылочный тип аргумента как тип параметра
// шаблона
f1(i); // i - это int; параметр шаблона Т - это int
f1(ci); // ci - это const int; параметр шаблона Т - это const int
f1(5); // ошибка: аргумент ссылочного параметра
// должен быть l-значением
Если параметр функции имеет тип const Т&
, обычные правила привязки гласят, что можно передать любой вид аргумента — объект (константный или нет), временный объект или литеральное значение. Когда сам параметр функции является константой, выведенный для параметра Т
тип не будет константным типом. Константность является частью типа параметра функции, и поэтому она не становится также частью типа параметра шаблона:template void f2(const T&); // может получать r-значения
// параметр в f2() - это const &; const в аргументе неуместен
// в каждом из этих трех вызовов параметр функции f2() выводится
// как const int&
f2(i); // i - это int; параметр шаблона Т - это int
f2(ci); // ci - это const int, но параметр шаблона T - это int
f2(5); // параметр const & может быть привязан к r-значению;
// Т - это int
Дедукция типа из параметров ссылки на r-значения функцийКогда параметр функции является ссылкой на r-значение (см. раздел 13.6.1), т.е. имеет форму Т&&
, обычные правила привязки гласят, что этому параметру можно передать r-значение. При этом дедукция типа ведет себя таким же образом, как дедукция обычного ссылочного параметра функции на l-значение. Выведенный тип для параметра Т
— это тип r-значения:template void f3(T&&);
f3(42); // аргумент - это r-значение типа int; параметр
// шаблона Т - это int
Сворачивание ссылок и параметры ссылок на r-значенияПредположим, что i
является объектом типа int
. Можно подумать, что такой вызов, как f3(i)
, будет недопустим. В конце концов, i
— это l-значение, а ссылку на r-значение обычно нельзя связать с l-значением. Однако язык определяет два исключения из обычных правил привязки, которые позволяют это. На этих исключениях из правил основан принцип работы таких библиотечных функций, как move()
.