Типы последних двух аргументов, наряду со схемой, определяют типы замыкающих параметров. Этот вызов создает следующий экземпляр:
ostream&
print(ostream&, const int&, const strings, const int&);
Второе развертывание происходит в рекурсивном вызове функции print()
. В данном случае схема — это имя пакета параметров функции (т.е. rest
). Эта схема развертывается в разделяемый запятыми список элементов пакета. Таким образом, этот вызов эквивалентен следующему:
print(os, s, 42);
Развертывание пакета параметров функции print()
только разворачивало пакет на его составные части. При развертывании пакета параметров функции возможны и более сложные схемы. Например, можно было бы написать вторую функцию с переменным количеством аргументов, которая вызывает функцию debug_rep()
(см. раздел 16.3) для каждого из своих аргументов, а затем вызывает функцию print()
, чтобы вывести полученные строки:
//
template
ostream &errorMsg(ostream &os, const Args&... rest) {
//
return print(os, debug_rep(rest)...);
}
Вызов функции print()
использует схему debug_rep(rest)
. Эта схема означает, что функцию debug_rep()
следует вызвать для каждого элемента в пакете параметров функции rest
. Получившийся развернутый пакет будет разделяемым запятыми списком вызовов функции debug_rep()
. Таким образом, вызов
errorMsg(cerr, fcnName, code.num(), otherData, "other", item);
выполняется, как будто было написано:
print(cerr, debug_rep(fcnName), debug_rep(code.num()),
debug_rep(otherData), debug_rep("otherData"),
debug_rep(item));
Следующая схема, напротив, не была бы откомпилирована:
//
print(os, debug_rep(rest...)); //
//
Проблема здесь в том, что пакет rest
развернут в вызове функции debug_rep()
. Этот вызов выполнился бы так, как будто было написано:
print(cerr, debug_rep(fcnName, code.num(),
otherData, "otherData", item));
В этом развертывании осуществляется попытка вызова функции debug_rep()
со списком из пяти аргументов. Нет никакой версии функции debug_rep()
, соответствующей этому вызову. Функция debug_rep()
имеет постоянное количество аргументов, и нет никакой ее версии с пятью параметрами.
Схема при развертывании применяется по отдельности к каждому элементу в пакете.
Упражнение 16.56. Напишите и проверьте версию функции errorMsg()
с переменным количеством аргументов.
Упражнение 16.57. Сравните свою версию функции errorMsg()
с переменным количеством аргументов с функцией error_msg()
из раздела 6.2.6. Каковы преимущества и недостатки каждого подхода?
По новому стандарту можно использовать шаблоны с переменным количеством аргументов совместно с функцией
forward()
для написания функций, которые передают свои аргументы неизменными некой другой функции. Чтобы проиллюстрировать такие функции, добавим в класс StrVec
(см. раздел 13.5) функцию-член emplace_back()
. Такая функция-член библиотечных контейнеров является шаблоном-членом с переменным количеством аргументов (см. раздел 16.1.4), которая использует их для создания элементов непосредственно в области, управляемой контейнером.
Версия функции emplace_back()
для класса StrVec
также должна быть с переменным количеством аргументов, поскольку у класса string
много конструкторов, которые отличаются своими параметрами.
Поскольку желательно быть в состоянии использовать конструктор перемещения класса string
, необходимо будет также сохранять всю информацию о типах аргументов, переданных функции emplace_back()
.
Как уже упоминалось, сохранение информации типа — двухступенчатый процесс. Во-первых, для сохранения информации типа аргументов параметры функции emplace_back()
следует определить как ссылки на r-значение параметра типа шаблона (см. раздел 16.2.7):
class StrVec {
public:
template