Я изучаю поведение компоновщика С++ в отношении специализированных шаблонов. Я использую Microsoft Visual С++ 2010 для этих экспериментов. Я не знаю, совпадает ли поведение с другими инструментальными целями (например, gcc).
Вот первый фрагмент кода:
// bar.cpp
template <typename T> int foo() { return 1; }
int bar() { return foo<double>(); }
// main.cpp
template <typename T> int foo() { return 1; }
template <> int foo<double>() { return 2; }
int bar();
int main()
{
const int x = bar();
const int y = foo<double>(); // doesn't link
}
Ожидается, что этот код не связывается, потому что foo<double>()
имеет несколько определений, поскольку он создается экземпляром один раз в bar.cpp и один раз в main.cpp(по специализации). Тогда мы ожидаем, что если эта программа будет связывать, что bar()
и main()
будут использовать различные экземпляры foo()
, чтобы в конце мы имели бы x == 1 и y == 2.
Зафиксировать ошибку связи, объявив специализацию foo<double>()
как static
:
// bar.cpp
template <typename T> int foo() { return 1; }
int bar() { return foo<double>(); }
// main.cpp
template <typename T> int foo() { return 1; }
template <> static int foo<double>() { return 2; } // note: static
int bar();
int main()
{
const int x = bar(); // x == 1
const int y = foo<double>(); // y == 2
}
Теперь мы имеем x == 1 и y == 2, как мы и ожидали. (Примечание: мы должны использовать ключевое слово static
здесь: анонимное пространство имен не будет выполняться, поскольку мы не можем специализировать функцию шаблона в другом пространстве имен, чем его объявление.)
Теперь использование ключевого слова static
довольно неинтуитивно. Как правило, специализация foo<double>()
будет находиться где-то в файле заголовка и поэтому будет помечена как встроенная, как в следующем фрагменте:
// bar.cpp
template <typename T> int foo() { return 1; }
int bar() { return foo<double>(); }
// main.cpp
template <typename T> int foo() { return 1; }
template <> inline int foo<double>() { return 2; } // note: inline
int bar();
int main()
{
const int x = bar(); // x == 2
const int y = foo<double>(); // y == 2
}
Этот код теперь правильно связывается, и когда мы его запускаем, мы получаем x == 2 и y == 2. Это бит, который я нахожу удивительным: почему существует одно определение foo<double>()
? В чем смысл inline
в этом коде?
Последний фрагмент:
// bar.cpp
template <typename T> int foo() { return 1; }
int bar() { return foo<double>(); }
// main.cpp
template <typename T> int foo() { return 1; }
template <> inline int foo<double>() { return 2; } // note: inline
int bar();
int main()
{
const int x = bar(); // x == 1
// const int y = foo<double>(); // note: commented out
}
Этот случай на самом деле не удивляет: специализация foo<double>()
больше не создается в main.cpp(хотя объявление все еще существует), поэтому единственным оставшимся экземпляром является тот, который находится в bar.cpp.