声明无法解决“实例化后的显式专业化”错误

时间:2016-11-27 07:14:25

标签: c++ crtp explicit-specialization template-instantiation

假设我正在尝试使用Curiously Recurring Template Pattern创建我自己的boost :: filesystem :: path实现:

(为简洁起见,代码不完整,但使用GCC 4.8.4时会使用'g++ -std=c++11 -o mypath ./mypath.cpp'编译时出现问题

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()。但是我并不特别想玩w鼹鼠试图让我的所有方法都顺利进行。

在研究关于SO的类似问题时,我发现其中一个accepted answer表明我可以通过仅仅宣布我的专业化来巧妙地解决这个问题。但...

  1. mypath.cpp中我注释掉的template class PathBase<Path>;行对此问题没有影响,
  2. 有点像我的头文件已声明其整个class Path : public PathBase<Path> { ... }声明的显式特化。
  3. 我的明确声明究竟是什么样的?

1 个答案:

答案 0 :(得分:4)

让我们先解决这些问题:

  1. template class PathBase<Path>;未声明明确的专业化;它是显式实例化定义。您根据您提供的定义,请求编译器实例化PathBase<Path>及其定义的所有成员。在这种特定情况下,它确实没有任何区别。

    明确专业化的声明看起来像template<> class PathBase<Path>;,但这不是你想要的;见下文。

  2. 在定义PathBase<Path>时使用Path并不会声明显式专业化;它会根据您在上面提供的定义触发PathBase<Path>隐式实例化。类模板的隐式实例化实例化类定义,并仅实例化其成员函数的声明;它没有尝试实例化函数的定义;这些只在需要时才会实例化,以后再进行实例化。

  3. 在您的cpp文件中,您明确地将IsSeparatorSeparators专门用于隐式实例化的PathBase<Path>。您根据您提供的通用定义请求编译器实例化PathBase<Path>,但是,当需要这些特定函数的定义时,请使用您提供的特定定义。

    它基本上是显式专门化整个类模板的简写替代方法,当类的结构和成员的大多数通用定义都很好时,你只想微调a的定义。很少有成员。如果您明确专门化了整个类模板,那么您必须为专业化的所有成员函数提供单独的类定义和定义,这意味着不必要的复制粘贴。

    在某些代码尝试使用这些定义之前,您需要尽快告诉编译器这些显式特化(它需要知道它必须寻找特定的定义)通用的)。您可以通过声明(不一定定义)显式特化来实现。

    最安全的地方是在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的强制性标准报价:

      

    [...]在撰写专业时,请注意其位置;要么   使它编译将是一个试图点燃它的   自焚。

    是的,标准化委员会的成员也是人。