Язык программирования C++. Пятое издание - Стенли Липпман
Шрифт:
Интервал:
Закладка:
• debug_rep(const string&) — обычная, не шаблонная функция.
В данном случае у обеих функций одинаковый список параметров, поэтому каждая из них обеспечивает одинаково хорошее соответствие этому вызову. Однако выбирается нешаблонная версия. По тем же причинам, по которым предпочитаются наиболее специализированные из одинаково хорошо подходящих шаблонов функций, нешаблонная функция предпочитается при одинаково хорошем соответствии с шаблонной функцией.
Когда нешаблонная функция обеспечивает одинаково хорошее соответствие с шаблонной функцией, предпочитается нешаблонная версия.
Перегруженные шаблоны и преобразованияДо сих пор не рассматривался случай с указателями на символьные строки в стиле С и строковые литералы. Теперь, когда имеется версия функции debug_rep(), получающая строку, можно было бы ожидать, что ей будет соответствовать вызов, которому переданы символьные строки. Однако рассмотрим этот вызов:
cout << debug_rep("hi world!") << endl; // вызов debug_rep(T*)
Здесь подходящими являются все три функции debug_rep():
• debug_rep(const Т&) — с привязкой параметра Т к типу char[10];
• debug_rep(Т*) — с привязкой параметра Т к типу соnst char;
• debug_rep(const string&) — требующая преобразования из const char* в string.
Оба шаблона обеспечивают точное соответствие аргументу — второй шаблон требует (допустимого) преобразования из массива в указатель, и это преобразование считается точным соответствием при подборе функции (см. раздел 6.6.1). Нешаблонная версия является подходящей, но требует пользовательского преобразования. Эта функция хуже точного соответствия, поэтому кандидатами остаются два шаблона. Как и прежде, версия Т* более специализирована, она и будет выбрана.
Если символьные указатели необходимо обработать как строки, можно определить еще две перегруженные, нешаблонные функции:
// преобразовать символьные указатели в строку и вызвать строковую
// версию debug_rep()
string debug_rep(char *p) {
return debug_rep(string(p));
}
string debug_rep(const char *p) {
return debug_rep(string(p));
}
Пропуск объявления может нарушить программуСледует заметить, что для правильной работы версии char* функции debug_rep() объявление debug_rep(const string&) должно находиться в области видимости, когда эти функции определяются. В противном случае будет вызвана неправильная версия функции debug_rep():
template <typename Т> string debug_rep(const T &t);
template <typename T> string debug_rep(T *p);
// следующее объявление должно быть в области видимости
// для правильного определения debug_rep(char *)
string debug_rep(const string &);
string debug_rep(char *p) {
// если объявление для версии, получающей const string&, не находится
// в области видимости, return вызовет call debug_rep(const Т&) с
// экземпляром строки в параметре Т
return debug_rep(string(p));
}
Обычно, если попытаться использовать функцию, которую забыли объявлять, код не будет откомпилирован. Но с функциями, которые перегружают шаблон функции, все не так. Если компилятор может создать экземпляр вызова из шаблона, то отсутствие объявления не будет иметь значения. В этом примере, если забыть объявлять версию функции debug_rep(), получающую строку, компилятор тихо создаст версию экземпляра шаблона, получающую const Т&.
Объявляйте каждую функцию в наборе перегруженных, прежде чем определять их. Таким образом можно гарантировать, что компилятор создаст экземпляр вызова прежде, чем он встретит функцию, которую предполагалось вызвать.
Упражнения раздела 16.3Упражнение 16.48. Напишите собственные версии функций debug_rep().
Упражнение 16.49. Объясните, что происходит в каждом из следующих вызовов:
template <typename Т> void f(Т);
template <typename T> void f(const T*);
template <typename T> void g(T);
template <typename T> void g(T*);
int i = 42, *p = &i;
const int ci = 0, *p2 = &ci;
g(42); g(p); g(ci); g(p2);
f(42); f(p); f(ci); f(p2);
Упражнение 16.50. Определите функции из предыдущего упражнения так, чтобы они выводили идентификационное сообщение. Выполните код этого упражнения. Если вызовы ведут себя не так, как ожидалось, выясните почему.
16.4. Шаблоны с переменным количеством аргументов
Шаблон с переменным количеством аргументов (variadic template) — это шаблон функции или класса, способный получать переменное количество параметров. Набор таких параметров называется пакетом параметров (parameter pack). Есть два вида пакетов параметров: пакет параметров шаблона (template parameter pack), представляющий любое количество параметров шаблона, и пакет параметров функции (function parameter pack), представляющий любое количество параметров функции.
Для указания, что шаблону или функции представлен пакет параметров, используется многоточие. В списке параметров шаблона синтаксис class... или typename... означает, что следующий параметр представляет список любого количества типов; имя типа, сопровождаемое многоточием, представляет список из любого количества параметров значения заданного типа. Параметр в списке параметров функции, типом которого является пакет параметров шаблона, представляет собой пакет параметров функции. Например:
// Args - это пакет параметров шаблона; rest - пакет параметров функции
// Args представляет любое количество параметров типа шаблона
// rest представляет любое количество параметров функции
template <typename Т, typename... Args>
void foo(const T &t, const Args& ... rest);
Этот код объявляет, что fоо() — это функция с переменным количеством аргументов, у которой один параметр типа по имени T и пакет параметров шаблона по имени Args. Этот пакет представляет любое количество дополнительных параметров типа. В списке параметров функции foo() один параметр типа const& для любого типа переданного параметром Т и пакет параметров функции rest. Этот пакет представляет любое количество параметров функции.
Как обычно, компилятор выводит типы параметра шаблона из аргументов функции. Для шаблона с переменным количеством аргументов компилятор также выводит количество параметров в пакете. Рассмотрим, например, следующие вызовы:
int i = 0; double d = 3.14; string s = "how now brown cow";
foo(i, s, 42, d); // три параметра в пакете
foo(s, 42, "hi"); // два параметра в пакете
foo(d, s); // один параметр в пакете
foo("hi"); // пустой пакет
Компилятор создаст четыре разных экземпляра функции fоо():
void foo(const int&, const string&, const int&, const double&);
void foo(const string&, const int&, const char[3]&);
void foo(const double&, const string&);
void foo(const char[3]&);
В каждом случае тип T выводится из типа первого аргумента. Остальные аргументы (если они есть) представляют количество и типы дополнительных аргументов функции.
Оператор sizeof...Когда необходимо узнать, сколько элементов находится в пакете, можно использовать оператор sizeof.... Как и оператор sizeof (см. раздел 4.9), оператор sizeof... возвращает константное выражение (см. раздел 2.4.4) и не вычисляет свой аргумент:
template<typename ... Args> void g(Args ... args) {
cout << sizeof...(Args) << endl; // количество параметров типа
cout << sizeof...(args) << endl; // количество параметров функции
}
Упражнения раздела 16.4Упражнение 16.51. Определите, что возвратят операторы sizeof...(Args) и sizeof...(rest) для каждого вызова функции foo() в этом разделе.
Упражнение 16.52. Напишите программу, проверяющую ответы на предыдущий вопрос.
16.4.1. Шаблоны функции с переменным количеством аргументов
В разделе 6.2.6 упоминалось, что для определения функции, способной получать переменное количество аргументов, можно использовать класс initializer_list. Однако у аргументов должен быть одинаковый тип (или типы, преобразуемые в общий тип). Функции с переменным количеством аргументов используются тогда, когда не известно ни количество, ни типы аргументов. Для примера определим функцию, подобную прежней функции error_msg(), только на сей раз обеспечим и изменение типов аргумента. Начнем с определения функции print() с переменным количеством аргументов, которая выводит содержимое заданного списка аргументов в указанный поток.