C++ class 模板中的友元比较和关系运算符

Friend comparison and relational operators in C++ class template

来自 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== 并实例化 class 模板。它成功编译和链接:

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

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

如果我在友元声明中删除 operator== 后面的 <T>,我会收到链接器错误:

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

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

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

1) 前向声明运算符

2) 将运算符声明为友元,插入额外的<T> 其功能我不明白

3) 定义运算符out-of-class.

因此我添加了以下代码:

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

如何为这个 class 成功定义运算符 <

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

why is the <T> present after ==? Clearly the <T> following operator== is necessary, but why?

因为友元声明中的operator==指的是函数模板,必须明确指定。否则会声明一个非模板函数,但后面找不到它的定义。这与调用(和实例化)函数模板的场景不同。

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

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

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

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

This produces a compilation error around operator<<T>

如你所说,应该写成friend bool operator< <T>(...),或者friend bool operator< <>(...),或者看我对非模板函数的建议朋友

First question: in the line

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

why is the <T> present after ==? Why not simply write

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

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

warning: friend declaration 'bool operator==(const Blob< <template-parameter-1-1> >&, const Blob< <template-parameter-1-1> >&)' declares a non-template function [-Wnon-template-friend]

您正在使非模板函数成为您 class 的朋友,因此 compiler/linker 将寻找非模板函数,在您的情况下:

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

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

如果你不添加<T>(或<>friend声明),你必须为每个类型定义一个函数,这可能不是你想要的。

Second question: If I want to define the relational less than operator < for the same class, I would guess that I should follow the pattern that worked for ==:

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

我发布自己的答案,感谢 Joachim Pileborg 和 sonyuanyao 的指导。

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

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
}

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

来自 C++14 标准 n4296,14.5.4(请参阅底部了解此处使用的术语摘要)。

For a friend function declaration that is not a template declaration:

(1.1) — if the name of the friend is a qualified or unqualified template-id, the friend declaration refers to a specialization of a function template, otherwise,

(1.2) — if the name of the friend is a qualified-id and a matching non-template function is found in the specified class or namespace, the friend declaration refers to that function, otherwise,

(1.3) — if the name of the friend is a qualified-id and a matching function template is found in the specified class or namespace, the friend declaration refers to the deduced specialization of that function template (14.8.2.6), otherwise,

(1.4) — the name shall be an unqualified-id that declares (or redeclares) a non-template function.

现在我们看第 5 行的好友声明,根据上面列出的四个步骤确定它指的是什么。

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

(1.2) == 不是合格的 id;继续...

(1.3) == 不是合格的 id;继续...

(1.4) 因此,== 是声明(或重新声明)非模板函数的非限定 ID。

根据标准的第 7.3.3 节,朋友 == 在最里面的封闭名称 space 中声明——在这种情况下,全局名称 space。

当我们在第 12 行实例化 Blob<int> 时,编译器通过用 int 替换 class Blob 中所有出现的 T 来生成源代码。编译器生成的代码中的友元声明如下:

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

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

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

解决方案是使用模板 ID(不是模板声明)作为友元声明中的名称:

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

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

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

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

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的结构:

simple-template-id:

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

template-id:

 simple-template-id

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

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

template-name:

 identifier

所以一些模板 ID 会包含 Foo<T>==<T>。 (== 是一个运算符函数 ID)。请注意,与模板声明不同,template <> 不包含在模板 ID 表达式中。