Ответ 1
Распространение источника на три файла просто путает вопрос: предварительная обработка делает один блок компиляции, а поведение просто зависит от содержимого 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)