Разрешение спецификации шаблона С++ и перегрузка

Я прочитал Почему не специализировать шаблоны функций и после эксперимента немного, Я нашел интересную вещь. Здесь перейдите main.cxx:

// main.cxx
#include <iostream>

// Declarations
/*
template<class T>
void foo(T);

template<>
void foo(int*);

template<class T>
void foo(T*);
*/

// Definition and specification
template<class T>
void foo(T x)
{
    std::cout << "T version." << std::endl;
}

template<>
void foo(int *i)
{
    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);
}

Интересная вещь: если я оставлю часть объявления прокомментированной, поведение будет таким же, как сказано в статье, т.е. версия T * будет использоваться, если определение версии int * предшествует ее определению, а также тиху и стиху. Однако, если раскомментировать блок объявления, будет вызываться только версия int * независимо от того, какой заказ я использую в определениях или объявлениях. Мой вопрос заключается в том, как это выражение влияет на разрешение?

Любые идеи? Я использую g++ 4.2.2 на x86_64-redhat-linux

EDIT: упростите этот вопрос после ответа AProgrammer

Ответ 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)