Rya*_*ell 3 c++ crtp explicit-specialization template-instantiation
假设我尝试使用奇怪的重复模板模式创建自己的 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
Run Code Online (Sandbox Code Playgroud)
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;
}
Run Code Online (Sandbox Code Playgroud)
当然,我发现编写的代码无法编译,因为我在隐式实例化它Separators()之后显式专门化IsSeparator()了它。但我并不是特别想玩打地鼠游戏,试图让我的所有方法都井井有条。
在研究 SO 上的类似问题时,我发现对其中一个问题的接受答案表明我只需声明我的专业即可巧妙地解决这个问题。但...
template class PathBase<Path>;行对问题没有影响,并且class Path : public PathBase<Path> { ... }声明声明了显式专业化。我的显式声明到底需要是什么样子?
让我们首先解决这些问题:
template class PathBase<Path>;没有声明明确的专业化;它是一个显式的实例化定义。您请求编译器PathBase<Path>根据您迄今为止提供的定义来实例化其具有定义的所有成员。在这种具体情况下,它确实没有任何区别。
显式专业化的声明看起来像template<> class PathBase<Path>;,但这也不是您想要的;见下文。
使用PathBase<Path>when 定义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;
Run Code Online (Sandbox Code Playgroud)
您肯定需要在头文件中而不是在 cpp 文件中执行此操作,否则使用该标头的其他 cpp 文件将不知道显式专业化,并会尝试实例化通用版本(如果需要的话)。这将使您的程序格式错误,无需诊断(这也适用于您的示例)。这意味着:如果编译器足够聪明,能够诊断问题,你应该感激不已;如果不是,你也不能抱怨,这仍然是你的错。
预先声明显式专业化后,可以稍后再定义定义,可能在单独的 cpp 文件中;没关系,就像正常功能一样。
另请注意,如果您想在头文件中包含显式专业化的定义(例如,为了简化内联),您必须再次声明它们inline,就像普通函数一样。否则,在多个 cpp 文件中包含标头将使程序格式错误,NDR(通常会在链接时出现多个定义错误)。
来自[temp.expl.spec]/7的强制性标准引用:
[...] 编写专业化时,请注意其位置;或者让它编译将是一个足以点燃其自焚的考验。
是的,标准化委员会的成员也是人。