Вот что я имею в виду:
// 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
ошибка, которая была новой для меня. Итак, я «обошел» эту ошибку, и теперь все работает нормально, но все же ..
Это четко определенное поведение?
редактировать: И то же самое для шаблонных функций, не являющихся членами (заранее объявленные функции, не являющиеся членами шаблона).
Гонки легкости на орбите цитируется почему это не соответствует части из Стандарта. Там могут быть некоторые другие, в непосредственной близости.
Я попытаюсь объяснить в более простых терминах, что означает стандартное словосочетание, и, надеюсь, я получу его правильно и, наконец, объясню ошибки компоновщика (или отсутствие ошибок):
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_
,
Примечание: мне не ясно, могла ли явная специализация дать другое искажение.
Нет, я не думаю, что это нормально
[C++11: 14/6]:
Шаблон функции, функция-член шаблона класса или статический член данных шаблона класса должны быть определены в каждой единице перевода, в которой он неявно создается (14.7.1), если только соответствующая специализация не создается явно (14.7.2) в какой-то переводчик; Диагностика не требуется.
[C++11: 14.7.3/6]:
Если шаблон, шаблон элемента или элемент шаблона класса явно специализированы, то эта специализация должна быть объявлена до первого использования этой специализации, которая вызовет неявную реализацию в каждой единице перевода, в которой такое использование происходит. ; Диагностика не требуется. [..]
Честно говоря, я не могу объяснить, почему это работает для вас.
Я думаю, что ваш исходный код был неправильным, и ваш «обходной путь» тоже не соответствует стандартам (несмотря на то, что ваш компилятор и компоновщик обрабатывают его). Хорошие цитаты из стандарта были приведены в ответ из @ 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
Я отметил свой ответ как вики сообщества, потому что на самом деле это только большой комментарий.