C++ - определение функции-члена вне шаблона-класса, но в заголовке
Я определил простой шаблон класса с одной функцией-членом. Он определен вне класса с дополнительной (явной) специализацией, также определенной вне класса. Все в одном заголовочном файле. Если вы включите этот заголовок в несколько блоков перевода, вы получите ошибку компоновщика из-за One-Definition-Rule.
// Header with a template
template <class T>
class TestClass
{
public:
TestClass() {};
~TestClass() {};
bool MemberFunction();
};
template <class T>
bool TestClass<T>::MemberFunction()
{
return true;
}
template <>
bool TestClass<double>::MemberFunction()
{
return true;
};
Пока все хорошо. Но если я помещу определение функции-члена в тело класса, ошибка компоновщика исчезнет, и функции можно будет использовать в разных единицах перевода.
// Header with a template
template <class T>
class TestClass
{
public:
TestClass() {};
~TestClass() {};
bool MemberFunction()
{
return true;
}
};
template <>
bool TestClass<double>::MemberFunction()
{
return true;
};
Мой вопрос: почему это так работает? Я использую MSVC 2012. У ODR есть некоторые исключения для шаблонов, что я сначала и назвал причиной. Но определение "базовой" функции внутри / вне класса имеет значение здесь.
1 ответ
14,7/5 говорит
5 Для заданного шаблона и заданного набора аргументов шаблона,
- явное определение экземпляра должно появиться не более одного раза в программе,
- явная специализация должна быть определена не более одного раза в программе (согласно 3.2), и
- как явное создание экземпляра, так и объявление явной специализации не должны появляться в программе, если только явное создание экземпляра не следует за объявлением явной специализации.
Реализация не требуется для диагностики нарушения этого правила.
Вторая пуля относится к вашему делу. ODR, определенный в 3.2, говорит то же самое, хотя и в менее дистиллированной форме.
Независимо от того, где и как определяется неспециализированная версия функции-члена, определение специализированной версии
template <> bool TestClass<double>::MemberFunction()
{
return true;
};
должен идти в .cpp
файл. Если он хранится в заголовочном файле, он вызовет нарушение ODR, как только заголовок будет включен в несколько блоков перевода. GCC достоверно обнаружит это нарушение. MSVC кажется менее надежным в этом отношении. Но, как указано в приведенной выше цитате, реализация не требуется для диагностики нарушения этого правила.
Заголовочный файл должен содержать только неопределяющее объявление этой специализации
template <> bool TestClass<double>::MemberFunction();
Тот факт, что в MSVC ошибка появляется или исчезает в зависимости от такого, казалось бы, не связанного фактора, как то, как определяется неспецифическая версия функции, должен быть особенностью компилятора MSVC.
После дальнейших исследований выясняется, что реализация MSVC фактически нарушена: ее поведение выходит за рамки того, что разрешено разрешением "не требуется никакой диагностики", заданным в спецификации языка.
Поведение, которое вы наблюдали в своих экспериментах, согласуется со следующим: объявление шаблона основной функции как inline
автоматически делает явную специализацию этого шаблона inline
также. Это не должно быть так. В 14.7.3/14 спецификация языка гласит:
Явная специализация шаблона функции является встроенной, только если он объявлен с помощью встроенного спецификатора или определен как удаленный, и независимо от того, является ли его шаблон функции встроенным.