Подтвердить что ты не робот

Связывание с С++ и специализированные шаблоны

Я изучаю поведение компоновщика С++ в отношении специализированных шаблонов. Я использую 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.

4b9b3361

Ответ 1

На самом деле вы нарушаете правило С++ (основное внимание):

14.7.3 [temp.expl.spec]:

6/ Если шаблон, шаблон-член или член шаблона класса явно специализирован, то эта специализация должна быть объявлена ​​до первого использования этой специализации, которая вызовет неявное создание экземпляра место в каждой единицы перевода, в которой такое использование происходит; не требуется диагностика. Если программа не дает определения для явной специализации, и либо специализация используется таким образом, чтобы вызвать неявное создание экземпляра, либо член является виртуальной функцией-членом, программа плохо сформирована, не требуется диагностика. Имплицитная инстанция никогда не создается для явной специализации, которая объявлена, но не определена. [Пример:

class String { };

template<class T> class Array { /* ... */ };
template<class T> void sort(Array<T>& v) { /* ... */ }

void f(Array<String>& v) {
  sort(v); // use primary template
  // sort(Array<T>&), T is String
}

template<> void sort<String>(Array<String>& v); // error: specialization
                                                // after use of primary template

template<> void sort<>(Array<char*>& v); // OK: sort<char*> not yet used

template<class T> struct A {
  enum E : T;
  enum class S : T;
};

template<> enum A<int>::E : int { eint }; // OK
template<> enum class A<int>::S : int { sint }; // OK
template<class T> enum A<T>::E : T { eT };
template<class T> enum class A<T>::S : T { sT };
template<> enum A<char>::E : int { echar }; // ill-formed,
                                            // A<char>::E was instantiated
                                            // when A<char> was instantiated
template<> enum class A<char>::S : int { schar }; // OK

-end пример]