C ++类模板中的朋友比较和关系运算符

时间:2016-07-25 08:25:25

标签: c++ templates operator-overloading friend

来自Lippman等人的C ++ Primer第5版,第16.1.2节:

//forward declarations needed for friend declarations in Blob
template <typename> class BlobPtr;
template <typename> class Blob;
template <typename T> bool operator==(const Blob<T>&, const Blob<T>&)

template <typename T> class Blob {
   friend class BlobPtr<T>;
   friend bool operator==<T>(const Blob<T>&, const Blob<T>&);
}

第一个问题:行

friend bool operator==<T>(const Blob<T>&, const Blob<T>&);

为什么<T>出现在==之后?为什么不简单地写

friend bool operator==(const Blob<T>&, const Blob<T>&);

我添加了以下代码来定义operator ==并实例化类模板。它成功编译并链接:

template <typename T>
bool operator==(const Blob<T> &lhs, const Blob<T> &rhs) {return true;}

int main() {
    Blob<int> a, b;
    a == b;
}

如果我删除了friend声明中<T>之后的operator==,则会收到链接器错误:

Undefined symbols for architecture x86_64: "operator==(Blob<int> const&, Blob<int> const&)", referenced from: _main in partial_blob-3ccda9.o

显然,<T>之后的operator==是必要的,但为什么呢?

第二个问题:如果我想为同一个类定义关系小于运算符<,我猜我应该遵循适用于==的模式:

1)转发声明运营商

2)将操作员声明为朋友,插入其功能我不明白的附加<T>

3)定义运营商外的。

因此我添加以下代码:

template <typename T> bool operator<(const Blob<T>&, const Blob<T>&);
template <typename T> class Blob {
   //other members as before
   friend bool operator<<T>(const Blob<T>&, const Blob<T>&);
}
bool operator<(const Blob<T>&, const Blob<T>&) {return true;}
int main() {
   //other statements as before
   a < b;
}

这会在operator<<T>周围产生编译错误,我想是因为编译器会将<<解释为插入运算符。但是,如果我将朋友声明重写为

friend bool operator<(const Blob<T>&, const Blob<T>&);

然后我得到一个链接器错误类似于==的早期链接器错误:

"operator<(Blob<int> const&, Blob<int> const&)", referenced from: _main in partial_blob-a85d5d.o

如何为此类成功定义运算符<

(注意:必须将运算符声明为朋友,因为更完全实现的实现依赖于私有变量。)

3 个答案:

答案 0 :(得分:2)

  

为什么<T>出现在==之后?显然<T>operator==是必要的,但为什么呢?

因为朋友声明中的operator==引用了函数模板,所以必须明确指定它。否则将声明非模板函数,但稍后无法找到它的定义。它与调用(和实例化)函数模板的方式不同。

注意T可以省略,但仍然需要<>。如:

// refers to a full specialization of operator==
friend bool operator== <>(const Blob<T>&, const Blob<T>&);

另一种候选方法是在类声明中定义运算符,它将是内联的,并且可以声明为非模板函数。如:

template <typename T> class Blob {
   ...
   friend bool operator==(const Blob&, const Blob&) { 
       return ...;
   }
}
  

这会在operator<<T>

附近产生编译错误

是的,正如您所说,应该写为friend bool operator< <T>(...)friend bool operator< <>(...),或者查看我对非模板功能朋友的建议。

答案 1 :(得分:0)

  

第一个问题:

行中
friend bool operator==<T>(const Blob<T>&, const Blob<T>&);
     

为什么<T>出现在==之后?为什么不简单地写

friend bool operator==(const Blob<T>&, const Blob<T>&);

如果删除<T>,gcc会发出警告:

  

警告:朋友声明'bool operator==(const Blob< <template-parameter-1-1> >&, const Blob< <template-parameter-1-1> >&)'声明非模板函数[-Wnon-template-friend]

您正在将非模板化函数作为您班级的朋友,因此编译器/链接器将在您的情况下寻找非模板化函数:

bool operator==(const Blob<int>&, const Blob<int>&);

...不存在,因此链接器无法找到它。

如果您没有将<T>(或<>添加到friend声明中),则必须为每种类型定义一个函数,这可能不是您想要的。

  

第二个问题:如果我想定义关系小于运算符&lt;对于同一个类,我猜我应该遵循适用于==:

的模式

这是解析C ++代码的方式的一个简单问题,您需要在operator<<<之间插入一个空格。这与C ++ 11之前存在的问题相同,由于vector<vector<int> >,您必须使用vector<vector<int>>而不是>>

答案 2 :(得分:0)

我发布了自己的答案,承认Joachim Pileborg和songyuanyao的指示。

我简化了代码,只关注问题1。 Pileborg和Holt正确地指出,重载<只需要一个空间来帮助编译器解析。

template <typename> class Blob;
template <typename T> bool operator==(const Blob<T>&, const Blob<T>&); //line 2

template <typename T> class Blob {
   friend bool operator==(const Blob<T>&, const Blob<T>&); //line 5
};

template <typename T>
bool operator==(const Blob<T> &lhs, const Blob<T> &rhs) {return true;} //line 9

int main() {
    Blob<int> a, b; //line 12
    a == b; //line 13
}

此代码在链接时产生错误。为了理解原因,我们将从标准中查看相关语言。

来自C ++ 14标准版n4296,14.5.4(参见下面的术语摘要)。

  

对于非模板声明的友元函数声明:

     

(1.1) - 如果朋友的名字是限定或不合格的模板ID,则朋友声明引用功能模板的特化,否则,

     

(1.2) - 如果朋友的名字是qualified-id并且在指定的类或命名空间中找到匹配的非模板函数,则friend声明引用该函数,否则,

     

(1.3) - 如果朋友的名字是qualified-id并且在指定的类或命名空间中找到匹配的函数模板,则friend声明引用该函数模板的推导特化(14.8.2.6),否则,

     

(1.4) - 名称应为声明(或重新声明)非模板函数的非限定id。

现在我们看看第5行的朋友声明,根据上面列出的四个步骤确定它所引用的内容。

(1.1)==不是模板ID;继续......

(1.2)==不是限定标识;继续......

(1.3)==不是限定ID;继续......

(1.4)因此,==是一个非限定id,用于声明(或重新声明)非模板函数。

根据标准的7.3.3节,朋友==在最里面的封闭命名空间中声明 - 在本例中是全局命名空间。

当我们在第12行实例化Blob<int>时,编译器通过用int替换类Blob中出现的所有T来生成源代码。然后编译器生成的代码中的friend声明读取:

friend bool operator==(const Blob<int>&, const Blob<int>&);

因此,我们在全局命名空间中声明了operator==的(非模板)重载,参数类型为const Blob<int>&

在第12行调用a == b时,编译器开始重载解析过程。它首先查找与参数类型匹配的任何非模板重载。它以operator==实例化时声明的Blob<int>形式找到完美匹配。接着然后链接器查找与最佳匹配声明相对应的operator==的定义,但它找不到,因为operator==(const Blob<int>&, const Blob<int>&)从未实际定义过!

解决方案是使用template-id(不是模板声明)作为friend声明中的名称:

friend bool operator== <>(const Blob<T>&, const Blob<T>&)

friend bool operator== <T>(const Blob<T>&, const Blob<T>&)

operator== <>operator== <T>都是模板ID(参见下面的术语);前者具有从函数参数列表推导出的隐式模板参数列表,后者具有显式模板参数列表。

在第12行实例化Blob<int>时,编译器会为友元声明生成以下代码:

friend bool operator== <>(const Blob<int>&, const Blob<int>&)

friend bool operator== <int>(const Blob<int>&, const Blob<int>&)

在任何一种情况下,朋友的名字都是不合格的模板ID,所以在上面的(1.1)中,朋友声明是指函数模板的特化。然后,编译器会找到所请求的<int>特化的最佳模板匹配。它找到的唯一模板是第2行中声明的模板,并在第9行中定义,并根据需要调用它。

术语

qualified-id:具有附加范围操作符的标识符,例如std::string::i

unqualified-id:没有附加范围操作符的标识符,例如stringi

template-id:以下摘录(来自C ++ 14 Standard n4296,14.2)总结了template-id的结构:

  

简单模板id:

 template-name < template-argument-list (opt)>
     

模板id:

 simple-template-id

 operator-function-id < template-argument-listopt >

 literal-operator-id < template-argument-listopt>
     

模板名称:

 identifier

因此,某些模板ID会包含Foo<T>==<T>。 (==是operator-function-id)。请注意,与模板声明不同,template <>不包含在template-id表达式中。