Предположим, я пытаюсь создать собственную реализацию boost :: filesystem :: path, используя Любопытно повторяющийся шаблон:
(Для краткости приведен код, но он будет содержать проблему, указанную при компиляции сg++ -std=c++11 -o mypath ./mypath.cpp
‘, используя GCC 4.8.4)
mypath.hpp:
#ifndef MYPATH_HPP
#define MYPATH_HPP
#include <string>
#include <vector>
namespace my {
template <class T>
class PathBase
{
public:
PathBase();
PathBase(std::string const& p);
std::string String() const;
bool IsSeparator(char c) const;
std::string Separators() const;
typedef std::vector<std::string> pathvec;
protected:
pathvec _path;
private:
virtual std::string _separators() const =0;
};class Path : public PathBase<Path>
{
public:
Path();
Path(std::string const& p);
private:
virtual std::string _separators() const final;
};
} // namespace 'my'
#endif // MYPATH_HPP
mypath.cpp:
#include "mypath.hpp"
namespace my {
//////////template class PathBase<Path>;
template<>
bool PathBase<Path>::IsSeparator(char c) const
{
return (Separators().find(c) != std::string::npos);
}
template <>
std::string PathBase<Path>::Separators() const
{
return _separators();
}
} // namespace
int main(int argc, char** argv)
{
return 0;
}
Конечно, я обнаружил, что написанный код не будет компилироваться, поскольку я явно специализируюсь Separators()
после IsSeparator()
неявно инстанцировал это. Но я не особенно хочу играть в биву, пытаясь держать все мои методы в выгодном порядке.
Исследуя подобные вопросы по SO, я обнаружил, что это принятый ответ один из них предположил, что я могу решить эту проблему аккуратно, просто объявив о своей специализации. Но…
template class PathBase<Path>;
строка в mypath.cpp не оказала влияния на проблему, иclass Path : public PathBase<Path> { ... }
декларация. Как должна выглядеть моя явная декларация?
Давайте сначала уберем их с пути:
template class PathBase<Path>;
не объявляет явную специализацию; это явное определение экземпляра. Вы запрашиваете создание экземпляра компилятора PathBase<Path>
и все его члены, для которых у него есть определения, основанные на определениях, которые вы предоставили до этого момента. В этом конкретном случае это не имеет никакого значения действительно.
Объявление явной специализации будет выглядеть так template<> class PathBase<Path>;
но это не то, что вы хотите здесь тоже; увидеть ниже.
Использование PathBase<Path>
при определении Path
также не объявляет явную специализацию; это вызывает неявная реализация из PathBase<Path>
на основе определения, которое вы предоставили выше. Неявное создание экземпляра для шаблона класса создает определение класса и только объявления его функций-членов; он не пытается создавать экземпляры определений функций; они создаются только при необходимости, позже.
В вашем файле cpp вы явно специализируете IsSeparator
а также Separators
для неявно созданного экземпляра PathBase<Path>
, Вы запрашиваете создание экземпляра компилятора PathBase<Path>
на основе предоставленного вами общего определения, но, когда нужны определения этих конкретных функций, используйте предоставленные вами конкретные определения.
По сути, это краткая альтернатива явной специализации всего шаблона класса, когда структура класса и большинство общих определений для членов хороши, а вам нужно только настроить определения нескольких членов. Если вы явно специализировали весь шаблон класса, вам нужно было бы предоставить отдельное определение класса и определения для всех функций-членов специализации, что означало бы ненужное копирование-вставку.
Вы должны сообщить компилятору об этих явных специализациях как можно скорее, прежде чем появится какая-либо вероятность того, что какой-то код попытается использовать определения (ему нужно знать, что ему придется искать конкретные определения вместо общих). Вы делаете это, объявляя (не обязательно определяя) явные специализации.
Самое безопасное место для этого — сразу после закрывающей скобки определения template <class T> class PathBase
, Что-то вроде:
class Path;
template<> std::string PathBase<Path>::Separators() const;
template<> bool PathBase<Path>::IsSeparator(char c) const;
Вам определенно нужно сделать это в файле заголовка, а не в файле cpp, иначе другие файлы cpp, которые используют заголовок, не будут знать о явных специализациях и будут пытаться создавать экземпляры универсальных версий (если они им нужны). Это сделает вашу программу плохо сформированной, диагностика не требуется (это относится и к вашему примеру). Это означает следующее: если компилятор достаточно умен, чтобы диагностировать проблему, вы должны быть благодарны; если это не так, вы не можете жаловаться, и это по-прежнему ваша вина.
Объявив явные специализации заранее, определения могут появиться позже, возможно, в отдельном файле cpp; это нормально, как и для обычных функций.
Также обратите внимание, что, если вы хотите включить определения для явных специализаций в файл заголовка (например, для облегчения встраивания), вам придется объявить их inline
, опять же, как для нормальных функций. В противном случае, включение заголовка в несколько файлов cpp сделает программу плохо сформированной, NDR (как правило, вы получите несколько ошибок определения во время компоновки).
Обязательная стандартная цитата из [Temp.expl.spec] / 7:
[…] При написании специализации будьте осторожны с ее местоположением; или же
сделать его будет таким испытанием, чтобы разжечь его
самосожжения.
Да, члены комитета по стандартизации тоже люди.
Других решений пока нет …