Первое исключение относится к дедукции типа для ссылочного параметра на r-значение. Когда l-значение (например, i) передается параметру функции, являющемуся ссылкой на r-значение на параметр типа шаблона (например, Т&&), компилятор выводит параметр типа шаблона как тип ссылки на l-значение аргумента. Поэтому, когда происходит вызов f3(i), компилятор выводит тип Т как int&, а не int.

Выведение типа Т как int&, казалось бы, означает, что параметр функции f3() будет ссылкой на r-значение типа int&. Обычно нельзя (непосредственно) определить ссылку на ссылку (см. раздел 2.3.1). Но это можно сделать косвенно, через псевдоним типа (см. раздел 2.5.1) или через параметр типа шаблона.

В таких ситуациях проявляется второе исключение из обычных правил привязки: если косвенно создать ссылку на ссылку, то эти ссылки "сворачиваются" (collapse). Во всех случаях кроме одного сворачивание ссылок формирует обычный тип ссылки на l-значение. Новый стандарт дополняет правила свертывания, включая ссылки на r-значение. Ссылки сворачиваются, формируя ссылку на r-значение только в специфическом случае ссылки на r-значение на ссылку на r-значение. Таким образом, для данного типа X:

• X& &, X& && и X&& & сворачиваются в тип X&.

• Тип X&& && сворачивается в тип X&&.

Сворачивание ссылок применимо только тогда, когда ссылка на ссылку создается косвенно, как в псевдониме типа или параметре шаблона.

Комбинация правил свертывания ссылок и специального правила дедукции типа для ссылочных на r-значения параметров означает, что можно вызвать функцию f3() для l-значения. Когда параметру функции f3() (ссылке на r-значение) передается l-значение, компилятор выведет тип T как тип ссылки на l-значение:

f3(i);  // аргумент - это l-значение; параметр Т шаблона - это int&

f3(ci); // аргумент - это l-значение;

        // параметр Т шаблона - это const int&

Когда параметр T шаблона выводится как ссылочный тип, правило свертывания гласит, что параметр функции T&& сворачивается в тип ссылки на l-значение. Например, результирующий экземпляр для вызова f3(i) получится примерно таким:

// недопустимый код, приведен только для примера

void f3(int& &&); // когда T - это int&, параметр

                        // функции - это int& &&

Параметр функции f3() — это Т&&, а T — это int&, таким образом, Т&& будет int& &&, что сворачивается в int&. Таким образом, даже при том, что формой параметра функции f3() будет ссылка на r-значение (т.е. T&&), этот вызов создаст экземпляр функции f3() с типом ссылки на l-значение (т.е. int&):

void f3(int&); // когда T - это int&, параметр функции

                     // сворачивается в int&

У этих правил есть два важных следствия.

• Параметр функции, являющийся ссылкой на r-значение для параметра типа шаблона (например, Т&&), может быть связан с l-значением.

• Если аргумент будет l-значением, то выведенный тип аргумента шаблона будет типом ссылки на l-значение, и экземпляр параметра функции будет создан как (обычный) параметр ссылки на l-значение (Т&).

Стоит также обратить внимание на то, что параметру функции Т&& косвенно можно передать аргумент любого типа. Параметр такого типа может использоваться с r-значениями, а, как было продемонстрировано только что, также и с l-значениями.

Параметру функции, являющемуся ссылкой на r-значение на тип параметра шаблона (т.е. Т&&), может быть передан аргумент любого типа. Когда такому параметру передается l-значение, экземпляр параметра функции создается как обычная ссылка на l-значение (T&).

Шаблоны функций с параметрами ссылки на r-значения

У того факта, что параметр шаблона может быть выведен как ссылочный тип, имеются удивительные последствия для кода в шаблоне:

template void f3(Т&& val) {

 T t = val;  // копировать или привязать ссылку?

 t = fcn(t); // изменит ли присвоение только t или val и t?

 if (val == t) { /* ... */ } // всегда истинно, если Т - ссылочный тип

}

Когда вызов функции f3() происходит для такого r-значения, как литерал 42, T имеет тип int. В данном случае локальная переменная t имеет тип int и инициализируется при копировании значения параметра val. При присвоении переменной t параметр val остается неизменным.

Перейти на страницу:

Похожие книги