3

我对在模板类中声明的朋友运算符和类的显式实例有一个很好的问题,如下所示:

template<class T>
class IneqCmp{
    friend bool operator!=(T a, T b){
        return !(a==b);
    }
};

struct IntCont{
    int x;
    bool operator==(IntCont that) const{
        return x==that.x;
    }
};
template class IneqCmp<IntCont>;
int main(){
    IntCont a{1}, b{2};
    while(a!=b);
}

我认为 operator!=(IntCont, IntCont) 应该在 IneqCmp<IntCont> 的实例化点进行实例化,但事实并非如此。为什么?

4

1 回答 1

6

上面代码的问题不在于是否定义,而是查找*operator!=永远找不到它。

当你将一个函数声明为一个类的友元时,这个声明很奇怪,因为它声明了一个命名空间级别的函数,但它的声明只能通过 ADL 在封闭类型上获得。因为您operator!=不接受IneqComp这两个参数中的任何一个,所以实际上不可能通过查找找到。

如果您想要做的是提供operator!=可用于一组类型的默认实现,您可以用不同的方式解决问题。

首先,您可以IneqCmp使用继承添加到 ADL 集中:

struct IntCont : IneqCmp<IntCont> {
   // ...
};

因为 ADL 在参数的类和基类中查找,这将有效地触发内部查找,从而IneqCmp<IntCont>找到friend声明并因此找到自由函数。

另一种选择是添加一个命名空间,其中operator!=将定义泛型(这样就不会在其他情况下找到它)和一个tag类型。然后从该标签继承:

namespace inequality_comparable {
   struct tag {};
   template <typename T>
   bool operator!=( T const & a, T const & b ) {
      return !(a==b);
   }
}
struct IntCont : inequality_comparable::tag {
};
bool operator==( IntCont const & a, IntCont const & b ) {
   return ...;
}
int main() {
    IntCont a,b;
    std::cout << a != b << "\n";
}

这里的诀窍是,因为inequality_comparable::tag它是您类型的基础,所以它会将命名空间添加inequality_comparable到查找中。当编译器遇到它时a != bmain它会尝试使用operator!=当前作用域中定义的类、类IntCont及其基类以及定义其基类的命名空间IntCont


*如果要验证算子是否真的生成了,尝试添加:

bool operator!=(IntCont a, IntCont b){
    return !(a==b);
}

如果由于 then 的实例化而定义了运算符IneqCmp,则会触发编译器错误。或者,您可以编译并检查生成的符号目标文件。

于 2012-08-17T21:01:40.593 回答