Распределение источника по трем файлам только запутывает дело: предварительная обработка делает одну единицу компиляции, а поведение зависит только от содержимого CU, а не от того, сколько файлов оно было распределено.
Я думаю, что вы удивлены, что в этом случае
#include <iostream>
template<class T> void foo(T); // A
template<> void foo(int*); // 1
template<class T> void foo(T*); // B
template<class T> void foo(T x)
{ std::cout << "T version." << std::endl; }
template<> void foo(int *i) // 2
{ std::cout << "int* version." << std::endl; }
template<class T> void foo(T *x)
{ std::cout << "T* version" << std::endl; }
int main(int argc, char** argv) {
int *p;
foo(p);
}
вы получите int* version
. Это ожидаемое поведение. Хотя (1) объявляет специализацию template <typename T> void foo(T)
, (2) не является определением этой специализации. (2) определяет и объявляет специализацию template<class T> void foo(T*);
, которая затем вызывается в main()
. Это произойдет, если вы дадите три объявления перед тремя определениями, в какие бы вы не поместили объявления и определения. Определение (2) всегда будет видеть объявление template<class T> void foo(T*);
и, следовательно, будет его специализацией.
Когда специализация для шаблона функции объявлена или определена, и это может быть специализация для нескольких шаблонов функций (как здесь (2) может быть специализация двух перегрузок A и B, их просто нужно объявить), это специализация "более специализированного". Вы можете увидеть точное определение «более специализированного» в стандартном разделе 17.5.5.2, но довольно легко увидеть, что B лучше соответствует A для (2) и, следовательно, (2) является специализацией (B) , (1) объявляет специализацию (A), потому что когда (1) объявлено, (B) еще не было замечено. Если вы хотите дать определение (1) после того, как (B) было замечено, вам нужно написать
template <> void foo<int*>(int*) // definition for (1)
{ std::cout << "foo<int*>(int*)\n"; }
Вы также можете быть явным при определении (2):
template<> void foo<int>(int *i) // 2 alternate
{ std::cout << "int* version." << std::endl; }
(но очевидно, что (2) и эта альтернативная версия в том же CU даст вам ошибку).
Вы также можете быть явным при вызове функций:
foo(p); // call (2)
foo<int>(p); // call (2)
foo<int*>(p); // call (1)