os << t << ", "; //
return print(os, rest...); //
//
}
Первая версия функции print()
останавливает рекурсию и выводит последний аргумент в начальном вызове функции print()
. Вторая версия, с переменным количеством аргументов, выводит аргумент, связанный с t
, и вызывает себя для вывода остальных значений в пакете параметров функции.
Ключевая часть — вызов функции print()
в функции с переменным количеством аргументов:
return print(os, rest...); // рекурсивный вызов; вывод других
// аргументов
Версия функции print()
с переменным количеством аргументов получает три параметра: ostream&
, const T&
и пакет параметров. Но в этом вызове передаются только два аргумента. В результате первый аргумент в пакете rest
привязывается к t
. Остальные аргументы в пакете rest
формируют пакет параметров для следующего вызова функции print()
. Таким образом, при каждом вызове первый аргумент удаляется из пакета и становится аргументом, связанным с t. Соответственно, получаем:
print(cout, i, s, 42); //
Рекурсия выполнится следующим образом:
Вызов | t | rest... |
---|---|---|
print(cout, i, s, 42) | i | s, 42 |
print(cout, s, 42) | s | 42 |
Вызов print(cout, 42)
вызывает обычную версию функции print()
.
Первые два вызова могут соответствовать только версии функции print()
с переменным количеством аргументов, поскольку обычная версия не является подходящей. Эти вызовы передают четыре и три аргумента соответственно, а обычная функция print()
получает только два аргумента.
Для последнего вызова в рекурсии, print(cout, 42)
, подходят обе версии функции print()
. Этот вызов передает два аргумента, и типом первого являются ostream&
. Таким образом, подходящей является обычная версия функции print()
.
Версия с переменным количеством аргументов также является подходящей. В отличие от обычного аргумента, пакет параметров может быть пустым. Следовательно, экземпляр версии функции print()
с переменным количеством аргументов может быть создан только с двумя параметрами: один — для параметра ostream&
и другой — для параметра const T&
.
Обе функции обеспечивают одинаково хорошее соответствие для вызова. Однако нешаблонная версия с переменным количеством аргументов более специализирована, чем шаблонная с переменным количеством аргументов. Поэтому выбирается версия без переменного количества аргументов (см. раздел 16.3).
print()
с постоянным количеством аргументов должно быть в области видимости, когда определяется версия с переменным количеством аргументов. В противном случае функция с переменным количеством аргументов будет рекурсивно вызывать себя бесконечно.
Упражнение 16.53. Напишите собственные версии функций print()
и проверьте их, выводя один, два и пять аргументов, у каждых из которых должны быть разные типы.
Упражнение 16.54. Что происходит при вызове функции print()
для типа, не имеющего оператора <<
?
Упражнение 16.55. Объясните, как выполнилась бы версия функции print()
с переменным количеством аргументов, если бы обычная версия функции print()
была объявлена после определения версии с переменным количеством аргументов.
Кроме выяснения размера, единственное, что можно еще сделать с пакетом параметров, — это ...
).
Например, функция print()
содержит два развертывания:
template
print(ostream &os, const T &t, const Args&... rest) //
//
{
os << t << ", ";
return print(os, rest...); //
}
В первом случае развертывание пакета параметров шаблона создает список параметров функции print()
. Второй случай развертывания находится в вызове функции print()
. Эта схема создает список аргументов для вызова.
Развертывание пакета Args
применяет схему const Args&
к каждому элементу в пакете параметров шаблона Args
. Результатом этой схемы будет разделенный запятыми список из любого количества типов параметров в формате const
. Например:
print(cout, i, s, 42); //