在类头文件之外实现模板运算符重载

5
以下代码定义在'util.h'中,可以编译和链接。但是当我将运算符重载的实现移动到'util.cc'时,链接器无法解析符号。这是否可能做到,还是由于模板的性质而无法完成?
谢谢。

正常工作

util.h

template<class T>
struct Rect {
  T x, y, w, h;

  friend bool operator ==(const Rect<T> &a, const Rect<T> &b) {
    return (a.x == b.x && a.y == b.y && a.w == b.w && a.h == b.h);
  }

  friend bool operator !=(const Rect<T> &a, const Rect<T> &b) {
    return !(a == b);
  }
};

无法工作

util.h

template<class T>
struct Rect {
  T x, y, w, h;

  friend bool operator ==(const Rect<T> &a, const Rect<T> &b);
  friend bool operator !=(const Rect<T> &a, const Rect<T> &b);
};

util.cc

template<class T>
bool operator ==(const Rect<T> &a, const Rect<T> &b)
{
    return (a.x == b.x && a.y == b.y && a.w == b.w && a.h == b.h);
}

template<class T>
bool operator !=(const Rect<T> &a, const Rect<T> &b)
{
    return !(a == b);
}

@rhalbersma:没错,我漏了。谢谢你指出来。实际上,我在想如果只是将这些运算符模板移到头文件中是否会起作用。我认为不应该。 - Andy Prowl
@rhalbersma:是的,我知道这一点,我在想的是,在这种情况下,将那些自由运算符模板移动到头文件中不应该有帮助,对吧?我的意思是,类模板实际上正在声明非模板运算符为友元,而这些运算符与全局运算符模板不同。因此,链接器最终应该会抱怨未解析的引用,即使全局运算符模板被放在头文件中。 - Andy Prowl
@AndyProwl 不行,因为你会用模板语法来定义非模板函数。 - TemplateRex
@AndyProwl 是的,因此重载决议会更喜欢未定义的非模板版本。 - TemplateRex
我不同意这是链接问题的重复。这个问题涉及独立模板(非类)运算符的模板化,而另一个问题讨论头文件中的模板。 - Syndog
显示剩余5条评论
1个回答

6
您实际问题的答案是否定的,原因在@AndyProwl的链接中有解释(请参见此处)。即使您将运算符定义包含在头文件util.h中,答案仍然是否定的。
原因是这些运算符实际上是非模板函数,它们作为类模板实例化的一个副作用而被注入到它们所定义的类的封闭范围内。换句话说,这个类模板...
template<class T>
struct Rect {
  T x, y, w, h;

  friend bool operator ==(const Rect<T> &a, const Rect<T> &b);
  friend bool operator !=(const Rect<T> &a, const Rect<T> &b);
};

针对每种类型 T,生成以下两个非模板函数:

bool operator ==(const Rect<T> &a, const Rect<T> &b);
bool operator !=(const Rect<T> &a, const Rect<T> &b);

如果您还定义了函数模板
template<class T>
bool operator ==(const Rect<T> &a, const Rect<T> &b)
{
    return (a.x == b.x && a.y == b.y && a.w == b.w && a.h == b.h);
}

template<class T>
bool operator !=(const Rect<T> &a, const Rect<T> &b)
{
    return !(a == b);
}

如果在代码中调用它们,并且名称查找和参数推断没有困难,但是函数重载解析将以平局结束,这将由非模板类友元函数进行打破。但当它们没有被定义时,程序将无法链接

有两种方法可以解决:在类中定义运算符(如您已经提供的)或(正如@AndyProwl所暗示的那样)使用此特殊语法在类内部作为通用运算符模板的友元。

// forward declarations
template<typename T> struct Rect;
template<typename T> bool operator==(const Rect<T>&, const Rect<T>&);
template<typename T> bool operator!=(const Rect<T>&, const Rect<T>&);

template<class T>
struct Rect {
  T x, y, w, h;

  // friend of operator templates
  friend bool operator == <>(const Rect<T> &a, const Rect<T> &b);
  friend bool operator != <>(const Rect<T> &a, const Rect<T> &b);
};

// definitions of operator templates follow

请注意,与模板交朋友是一件棘手的事情,正如Herb Sutter在这篇旧专栏中所解释的那样。 old column

1
我正在寻找关于运算符特殊情况的解释! - Etherealone
如果我有一堆枚举类,而不是一个Rect类,会怎样呢? - user6376109

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接