Разберите выбор перегрузки и назовите вывод программы. Как механизм частичного упорядочивания шаблонов влияет на вызов f(&value)?
#include <iostream>
template<class T>
void f(T) { std::cout << "general"; }
template<class T>
void f(T*) { std::cout << "pointer"; }
int main() {
int value = 0;
f(&value);
}
Программа выведет pointer. Для аргумента типа int* подходят обе шаблонные перегрузки, но f(T*) считается более специализированной: она описывает только указатели, тогда как f(T) может принять значение любого типа.
Шаблонные функции в C++ создавались для написания обобщённого алгоритма один раз, но на практике часто требовалась более точная реализация для отдельных форм типов. Поэтому шаблоны участвуют в обычном разрешении перегрузок, а компилятор умеет сравнивать их шаблонные параметры через частичное упорядочивание.
Этот механизм позволяет добавлять специализированную перегрузку без явной специализации функции. В отличие от полной специализации, такая перегрузка остаётся самостоятельным кандидатом и проходит обычные этапы выбора функции.
Для вызова f(&value) тип аргумента равен int*. Шаблон f(T) выводит T как int*, а шаблон f(T*) — как int; значит, оба шаблона успешно подставляются.
Одной только успешной подстановки недостаточно: компилятору нужно выбрать единственного кандидата. Ошибочное предположение, что будет вызвана первая объявленная функция или что шаблоны равноправны, приводит к неверному пониманию результата и может нарушить выбор специализированного поведения в библиотечном коде.
Сначала компилятор проверяет применимость кандидатов:
f(T) параметр T выводится как int*;f(T*) параметр T выводится как int.Затем выполняется частичное упорядочивание шаблонов функций. Условная форма T может описать любой тип, включая указатель. Форма T* описывает только указатели, поэтому она более специализирована по сравнению с T.
Следовательно, выбирается f(T*), и выводом становится pointer. Это не частичная специализация функции: стандарт C++ не поддерживает частичные специализации шаблонов функций. Здесь используется отдельная перегруженная функция-шаблон.
Важно отличать этот этап от качества преобразования аргумента. В данном примере обе функции вызываются с одинаково подходящим аргументом, поэтому решающим становится именно частичное упорядочивание. Если более специализированные шаблоны не удаётся однозначно упорядочить, вызов может стать неоднозначным.
Практическое следствие: общую перегрузку можно оставить как запасной вариант, а для распознаваемой формы типа добавить более специализированную. При этом усложнение набора перегрузок повышает риск неоднозначностей, особенно при добавлении ссылок, const, массивов и пользовательских преобразований.
В библиотеке сериализации есть общий обработчик serialize(T), но указатели должны обрабатываться отдельно: например, записываться как адресуемое значение или как специальное значение null. Рассматривались два варианта.
Явная специализация функции кажется естественной, но она требует точного указания типа и не позволяет частично специализировать функцию для всех указателей. Кроме того, её взаимодействие с другими перегрузками может быть менее очевидным.
Перегрузка serialize(T*) непосредственно выражает требуемую форму типа. Она сохраняет вывод типа и участвует в частичном упорядочивании, поэтому для указателя выбирается автоматически, а для остальных типов используется общий шаблон.
Выбранная перегрузка serialize(T*) обычно лучше масштабируется: общая логика остаётся резервной, а специализированное поведение локализовано. Однако перед добавлением новых перегрузок нужно проверить пары типов на неоднозначность и отдельно решить, как должны обрабатываться const-указатели и указатели на функции.
Дополнительный вопрос: Что произойдёт при вызове f(nullptr)?
Ответ: Вызов будет неоднозначным или, точнее, шаблон f(T*) не сможет вывести T из аргумента типа std::nullptr_t, потому что nullptr не является указателем конкретного типа и не выполняет преобразование к T* на этапе вывода шаблонного параметра. Шаблон f(T) вывести сможет, поэтому в данном наборе перегрузок будет выбран f(T) и T станет std::nullptr_t.
Дополнительный вопрос: Как изменится выбор для аргумента типа const int*?
Ответ: Будет выбрана перегрузка f(T*), а T выведется как const int. Квалификатор const относится к объекту, на который указывает указатель, и входит в выведенный тип T; это не превращает аргумент в неподходящий для шаблона T*.
Дополнительный вопрос: Почему нельзя считать f(T*) частичной специализацией f(T)?
Ответ: Частичная специализация изменяет реализацию уже существующего шаблона для подмножества параметров, но для шаблонов функций она стандартом не поддерживается. Запись f(T*) объявляет новую перегруженную функцию-шаблон. Поэтому сначала формируется набор перегрузок, затем для конкретного вызова выполняются вывод типов, проверка применимости и частичное упорядочивание кандидатов.