Безопасно ли размещать определение специализации функции-члена шаблона (без тела по умолчанию) в исходном файле?

Вот что я имею в виду:

// test.h
class cls
{
public:
template< typename T >
void f( T t );
};

// test.cpp
template<>
void cls::f( const char* )
{
}

// main.cpp
int main()
{
cls c;

double x = .0;
c.f( x ); // gives EXPECTED undefined reference (linker error)

const char* asd = "ads";
c.f( asd ); // works as expected, NO errors

return 0;
}

Это совершенно нормально, верно?

Я начал сомневаться в этом, потому что я просто переехал specialization of '...' after instantiation ошибка, которая была новой для меня. Итак, я «обошел» эту ошибку, и теперь все работает нормально, но все же ..

Это четко определенное поведение?


редактировать: И то же самое для шаблонных функций, не являющихся членами (заранее объявленные функции, не являющиеся членами шаблона).

8

Решение

Гонки легкости на орбите цитируется почему это не соответствует части из Стандарта. Там могут быть некоторые другие, в непосредственной близости.

Я попытаюсь объяснить в более простых терминах, что означает стандартное словосочетание, и, надеюсь, я получу его правильно и, наконец, объясню ошибки компоновщика (или отсутствие ошибок):

  1. Какой смысл в создании экземпляра?
  2. Как компилятор выбирает специализацию?
  3. Что необходимо в момент создания экземпляра?
  4. Почему ошибка компоновщика?

1 / Какой смысл создания экземпляра?

Точкой реализации функции шаблона является точка, в которой она вызывается или на которую ссылаются (&std::sort<Iterator>) с все параметры шаблона выделены (*).

template <typename T>
void foo(T) { std::cout << typeid(T).name() << "\n"; }

int main() { foo(1); } // point of instantiation of "foo<int>(int)"

Однако это может быть отложено и, следовательно, не соответствует точному сайту вызова для шаблонов, вызываемых из других шаблонов:

template <typename T>
void foo(T) { std::cout << typeid(T).name() << "\n"; }

template <typename T>
void bar(T t) { foo(t); } // not a point of instantiation, T is still "abstract"
int main() { foo(1); } // point of instantiation of "bar<int>(int)"// and ALSO of "foo<int>(int)"

Эта задержка очень важна, поскольку позволяет писать:

  • ко-рекурсивные шаблоны (т.е. шаблоны, которые ссылаются друг на друга)
  • пользовательские специализации

(*) Грубо говоря, есть исключения, такие как не шаблонные методы шаблонного класса …


2 / Как компилятор выбирает специализацию?

В момент создания, компилятор должен иметь возможность:

  • решить, какой базовый шаблон функция для вызова
  • и, возможно, какую из его специализаций назвать

Этот старый GotW показывает беды специализаций … но вкратце:

template <typename T> void foo(T);   // 1
template <typename T> void foo(T*);  // 2

являются перегрузки, и каждый порождает отдельный семья возможных специализаций, из которых они база.

template <> void foo<int>(int);

это специализация 1, и

template <> void foo<int*>(int*);

это специализация 2.

Чтобы разрешить вызов функции, компилятор сначала выберет лучшую перегрузку, игнорируя шаблонные специализации, и затем, если он выбрал шаблонную функцию, проверьте, имеет ли она какую-либо специализацию, которая могла бы лучше применяться.


3 / Что необходимо в момент создания экземпляра?

Итак, из того, как компилятор разрешает вызов, мы понимаем, Зачем Стандарт определяет, что любая специализация должна быть объявлена до его первая точка создания. В противном случае это просто не будет рассматриваться.

Таким образом, в момент создания экземпляра нужно уже видеть:

  • объявление базовой функции шаблона, которая будет использоваться
  • декларация о выбранной специализации, если таковая имеется

Но что из определения?

Это не нужно. Компилятор предполагает, что он будет предоставлен позже в TU или другим TU полностью.

Примечание: он обременяет компилятор, потому что это означает, что он должен помнить все неявные экземпляры, с которыми он сталкивался и для которых он не мог выдавать тело функции, так что когда он, наконец, встречает определение, он может (наконец) испустить весь необходимый код для всех специализаций, с которыми он сталкивался. Интересно, почему был выбран именно этот подход, а также удивляюсь, почему даже в отсутствие extern Объявление TU может заканчиваться неопределенными функциональными телами.


4 / Почему ошибка компоновщика?

Поскольку определения не предоставлено, gcc доверяет вам предоставить его позже и просто отправляет вызов неразрешенному символу. Если вам случится связать с другим TU, который предоставляет этот символ, то все будет хорошо, в противном случае вы получите ошибку компоновщика.

Поскольку GCC следует за Itanium ABI мы можем просто посмотреть, как это искажает символы. Оказывается, что ABI не имеет никакого значения в искажающих специализациях и неявных реализациях, таким образом

cls.f( asd );

звонки _ZN3cls1fIPKcEEvT_ (который разбирает как void cls::f<char const*>(char const*)) и специализация:

template<>
void cls::f( const char* )
{
}

также производит _ZN3cls1fIPKcEEvT_,

Примечание: мне не ясно, могла ли явная специализация дать другое искажение.

4

Другие решения

Нет, я не думаю, что это нормально

[C++11: 14/6]: Шаблон функции, функция-член шаблона класса или статический член данных шаблона класса должны быть определены в каждой единице перевода, в которой он неявно создается (14.7.1), если только соответствующая специализация не создается явно (14.7.2) в какой-то переводчик; Диагностика не требуется.

[C++11: 14.7.3/6]: Если шаблон, шаблон элемента или элемент шаблона класса явно специализированы, то эта специализация должна быть объявлена ​​до первого использования этой специализации, которая вызовет неявную реализацию в каждой единице перевода, в которой такое использование происходит. ; Диагностика не требуется. [..]

Честно говоря, я не могу объяснить, почему это работает для вас.

3

Я думаю, что ваш исходный код был неправильным, и ваш «обходной путь» тоже не соответствует стандартам (несмотря на то, что ваш компилятор и компоновщик обрабатывают его). Хорошие цитаты из стандарта были приведены в ответ из @ Lightness Races на орбите. См. Также следующий пример из стандарта ([temp.expl.spec] 14.7.3 / 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

Я отметил свой ответ как вики сообщества, потому что на самом деле это только большой комментарий.

2
По вопросам рекламы [email protected]