Первое исключение относится к дедукции типа для ссылочного параметра на r-значение. Когда l-значение (например, i
) передается параметру функции, являющемуся ссылкой на r-значение на параметр типа шаблона (например, Т&&
), компилятор выводит параметр типа шаблона как тип ссылки на l-значение аргумента. Поэтому, когда происходит вызов f3(i)
, компилятор выводит тип Т
как int&
, а не int
.
Выведение типа Т
как int&
, казалось бы, означает, что параметр функции f3()
будет ссылкой на r-значение типа int&
. Обычно нельзя (непосредственно) определить ссылку на ссылку (см. раздел 2.3.1). Но это можно сделать косвенно, через псевдоним типа (см. раздел 2.5.1) или через параметр типа шаблона.
• X& &
, X& &&
и X&& &
сворачиваются в тип X&
.
• Тип X&& &&
сворачивается в тип X&&
.
Комбинация правил свертывания ссылок и специального правила дедукции типа для ссылочных на r-значения параметров означает, что можно вызвать функцию f3()
для l-значения. Когда параметру функции f3()
(ссылке на r-значение) передается l-значение, компилятор выведет тип T
как тип ссылки на l-значение:
f3(i); //
f3(ci); //
//
Когда параметр T
шаблона выводится как ссылочный тип, правило свертывания гласит, что параметр функции T&&
сворачивается в тип ссылки на l-значение. Например, результирующий экземпляр для вызова f3(i)
получится примерно таким:
//
void f3
//
Параметр функции f3()
— это Т&&
, а T
— это int&
, таким образом, Т&&
будет int& &&
, что сворачивается в int&
. Таким образом, даже при том, что формой параметра функции f3()
будет ссылка на r-значение (т.е. T&&
), этот вызов создаст экземпляр функции f3()
с типом ссылки на l-значение (т.е. int&
):
void f3
//
У этих правил есть два важных следствия.
• Параметр функции, являющийся ссылкой на r-значение для параметра типа шаблона (например, Т&&
), может быть связан с l-значением.
• Если аргумент будет l-значением, то выведенный тип аргумента шаблона будет типом ссылки на l-значение, и экземпляр параметра функции будет создан как (обычный) параметр ссылки на l-значение (Т&
).
Стоит также обратить внимание на то, что параметру функции Т&&
косвенно можно передать аргумент любого типа. Параметр такого типа может использоваться с r-значениями, а, как было продемонстрировано только что, также и с l-значениями.
Т&&
), может быть передан аргумент любого типа. Когда такому параметру передается l-значение, экземпляр параметра функции создается как обычная ссылка на l-значение (T&
).
У того факта, что параметр шаблона может быть выведен как ссылочный тип, имеются удивительные последствия для кода в шаблоне:
template
T t = val; //
t = fcn(t); //
if (val == t) { /* ... */ } //
}
Когда вызов функции f3()
происходит для такого r-значения, как литерал 42
, T
имеет тип int
. В данном случае локальная переменная t
имеет тип int
и инициализируется при копировании значения параметра val
. При присвоении переменной t параметр val
остается неизменным.