来自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
如何为此类成功定义运算符<
?
(注意:必须将运算符声明为朋友,因为更完全实现的实现依赖于私有变量。)
答案 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:没有附加范围操作符的标识符,例如string
或i
。
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表达式中。