Язык программирования C++. Пятое издание - Стенли Липпман
Шрифт:
Интервал:
Закладка:
При использовании с параметром функции, являющимся ссылкой на r-значение для параметра типа шаблона (Т&&), функция forward() сохраняет все подробности типа аргумента.
Перепишем первоначальную функцию, используя на этот раз функцию forward():
template <typename F, typename T1, typename T2>
void flip(F f, T1 &&t1, T2 &&t2) {
f(std::forward<T2>(t2), std::forward<T1>(t1));
}
Если происходит вызов функции flip(g, i, 42), то параметр i будет передан функции g(), поскольку int& и 42 будут переданы как int&&.
Подобно функции std::move(), для функции std::forward() не стоит предоставлять объявление using. Причина описана в разделе 18.2.3.
Упражнения раздела 16.2.7Упражнение 16.47. Напишите собственную версию функции обращения и проверьте ее, вызывав функции с параметрами ссылок на r-значение и l-значение.
16.3. Перегрузка и шаблоны
Шаблоны функций могут быть перегружены другими шаблонами или обычными, не шаблонными функциями. Как обычно, функция с тем же именем должна отличаться либо количеством, либо типом своих параметров.
На подбор функции (см. раздел 6.4) присутствие шаблона функции влияет следующими способами.
• В набор функций-кандидатов на вызов включаются любые экземпляры шаблона функции, для которой успешна дедукция аргумента шаблона (см. раздел 16.2).
• Шаблоны функций-кандидатов всегда подходящие, поскольку дедукция аргумента шаблона устранит все неподходящие шаблоны.
• Как обычно, подходящие функции (шаблонные и нешаблонные) ранжируются по преобразованиям, если таковые вообще имеются. Конечно, набор применимых преобразований при вызове шаблона функции весьма ограничен (см. раздел 16.2.1).
• Так же как обычно, если только одна функция обеспечивает наилучшее соответствие, она и выбирается. Но если одинаково хорошее соответствие обеспечивают несколько функций, то:
• если в наборе одинаково хороших соответствий есть только одна нешаблонная функция, то выбрана будет она;
• если в наборе нет нешаблонных функций, но есть несколько шаблонных, и одна из них более специализированна, чем любые другие, то будет выбран более специализированный шаблон функции;
• в противном случае вызов неоднозначен.
Правильное определение набора перегруженных шаблонов функций требует хорошего понимания отношений между типами и ограничений на преобразования, применимых к аргументам в шаблонах функций.
Создание перегруженных шаблоновВ качестве примера создадим набор функций, которые могли бы пригодиться во время отладки. Назовем отладочные функции debug_rep(), каждая из них возвратит строковое представление предоставленного объекта. Начнем с создания самой общей версии этой функции в качестве шаблона, получающего ссылку на константный объект:
// выводит любом тип, который иначе не обработать
template <typename Т> string debug_rep(const T &t) {
ostringstream ret; // см. раздел 8.3
ret << t; // использует оператор вывода Т для вывода представления t
return ret.str(); // возвращает копию строки, с которой связан ret
}
Эта функция применяется для создания строки, соответствующей объекту любого типа, у которого есть оператор вывода.
Теперь определим версию функции debug_rep() для вывода указателя:
// выводит указатели как их значение, сопровождаемое объектом,
// на который он указывает
// обратите внимание: эта функция не будет работать правильно с char*;
// см. раздел 16.3
template <typename Т> string debug_rep(T *p) {
ostringstream ret;
ret << "pointer: " << p; // выводит собственное значение указателя
if (p)
ret << " " << debug_rep(*p); // выводит значение, на которое
// указывает p
else
ret << " null pointer"; // или указывает, что p - нулевой
return ret.str(); // возвращает копию строки, с которой связан ret
}
Эта версия создает строку, содержащую собственное значение указателя и вызывает функцию debug_rep() для вывода объекта, на который указывает этот указатель. Обратите внимание, что эта функция не может использоваться для вывода символьных указателей, поскольку библиотека ввода-вывода определяет версию оператора << для значения указателя char*. Эта версия оператора << подразумевала, что указатель обозначает символьный массив с нулевым символом в конце и выводит содержимое массива, а не его адрес. Обработка символьных указателей рассматривается в разделе 16.3.
Эти функции можно использовать следующим образом:
string s("hi");
cout << debug_rep(s) << endl;
Подходящей для этого вызова является только первая версия функции debug_rep(). Второй версии требуется параметр в виде указателя, а в этом вызове передан не указатель. Нет никакого способа создать экземпляр шаблона функции, ожидающего тип указателя, из параметра, который не является указателем, поэтому дедукция аргумента терпит неудачу. Поскольку есть только одна подходящая функция, она и используется.
Если происходит вызов функции debug_rep() с указателем:
cout << debug_rep(&s) << endl;
то обе функции создают подходящие экземпляры:
• debug_rep(const string*&) — экземпляр первой версии функции debug_rep() с привязкой параметра Т к типу string*;
• debug_rep(string*) — экземпляр второй версии функции debug_rep() с привязкой параметра Т к типу string.
Точным соответствием для этого вызова является экземпляр второй версии функции debug_rep(). Создание экземпляра первой версии требует преобразования простого указателя в указатель на константу. Обычный подбор функции гласит, что следует предпочесть второй шаблон, и в действительности так и происходит.
Несколько подходящих шаблоновВ качестве другого примера рассмотрим следующий вызов:
const string *sp = &s;
cout << debug_rep(sp) << endl;
Здесь подходящими являются оба шаблона, и оба обеспечивают точное соответствие:
• debug_rep(const string*&) — экземпляр первой версии шаблона с привязкой параметра Т к типу const string*;
• debug_rep(const string*) — экземпляр второй версии шаблона с привязкой параметра Т к типу const string.
В данном случае обычный подбор функции не может различить эти два вызова. Можно было бы ожидать, что этот вызов будет неоднозначен. Однако благодаря специальному правилу для перегруженных шаблонов функций этот вызов решается как debug_rep(Т*), поскольку это более специализированный шаблон.
Причина для этого правила в том, что без него не было бы никакого способа вызвать версию функции debug_rep() для указателя на константу. Проблема в том, что к шаблону debug_rep(const Т&) подходит практически любой тип, включая типы указателя. Этот шаблон является более общим, чем debug_rep(Т*), который может быть вызван только для типов указателя. Без этого правила вызовы с передачей указателей на константу всегда будут неоднозначны.
Когда есть несколько перегруженных шаблонов, предоставляющих одинаково хорошее соответствие для вызова, предпочитается наиболее специализированная версия.
Не шаблон и перегрузка шаблонаДля следующего примера определим обычную, не шаблонную версию функции debug_rep(), выводящую строки в двойных кавычках:
// вывод строк в двойных кавычках
string debug_rep(const string &s) {
return '"' + s + '"';
}
Теперь, когда происходит вызов функции debug_rep() для строки:
string s("hi");
cout << debug_rep(s) << endl;
есть две одинаково хорошо подходящих функции:
• debug_rep<string>(const string&) — первый шаблон с привязкой параметра T к типу string;
• debug_rep(const string&) — обычная, не шаблонная функция.
В данном случае у обеих функций одинаковый список параметров, поэтому каждая из них обеспечивает одинаково хорошее соответствие этому вызову. Однако выбирается нешаблонная версия. По тем же причинам, по которым предпочитаются наиболее специализированные из одинаково хорошо подходящих шаблонов функций, нешаблонная функция предпочитается при одинаково хорошем соответствии с шаблонной функцией.
Когда нешаблонная функция обеспечивает одинаково хорошее соответствие с шаблонной функцией, предпочитается нешаблонная версия.