【问题标题】:Friend comparison and relational operators in C++ class templateC++ 类模板中的友元比较和关系运算符
【发布时间】:2016-11-28 12:10:19
【问题描述】:

来自 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>&);

为什么&lt;T&gt; 出现在== 之后?为什么不简单地写

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;
}

如果我在朋友声明中删除operator== 之后的&lt;T&gt;,我会收到链接器错误:

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

显然&lt;T&gt; 跟在operator== 后面是必须的,但为什么呢?

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

1) 前向声明操作符

2) 将运算符声明为朋友,插入附加的&lt;T&gt;,其功能我不明白

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&lt;&lt;T&gt; 附近产生编译错误,我认为是因为编译器将&lt;&lt; 解释为插入运算符。但是如果我将朋友声明重写为

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

然后我收到一个链接器错误,类似于之前== 的链接器错误:

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

我怎样才能成功地为这个类定义运算符&lt;

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

【问题讨论】:

  • 记住这样声明的友元函数不是成员函数,它们是非成员函数,没有&lt;T&gt;,函数是不完整的。至于operator&lt;&lt;T&gt;,请尝试在操作员名称和模板之间添加一个空格,例如operator&lt; &lt;T&gt;
  • 关于未定义的引用及其原因和解决方案(如您所知); stackoverflow.com/a/35891188/3747990
  • @JoachimPileborg - 您能否提供此语法介绍的参考?我什至不知道它叫什么,所以我很难找到它。是模板专业化吗?显式实例化?对这些字词的网络搜索没有产生有用的结果。

标签: c++ templates operator-overloading friend


【解决方案1】:

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

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

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并且在指定的类或命名空间中找到一个匹配的非模板函数,朋友声明引用那个函数,否则,

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

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

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

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

(1.2) == 不是合格ID;继续……

(1.3) == 不是合格ID;继续……

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

根据标准的第 7.3.3 节,朋友== 被声明在最内层的封闭命名空间中——在本例中为全局命名空间。

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

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

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

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

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

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

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

operator== &lt;&gt;operator== &lt;T&gt; 都是模板 ID(参见下面的术语);前者有一个从函数形参列表推导出来的隐式模板形参列表,后者有一个显式的模板形参列表。

Blob&lt;int&gt; 在第 12 行被实例化时,编译器为朋友声明生成以下代码:

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

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

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

术语

qualified-id:带有附加范围运算符的标识符,例如std::string::i

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

template-id:以下摘录(来自 C++14 标准 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&lt;T&gt;==&lt;T&gt;。 (== 是一个操作符函数 ID)。请注意,与模板声明不同,template &lt;&gt; 不包含在模板 ID 表达式中。

【讨论】:

    【解决方案2】:

    为什么&lt;T&gt; 出现在== 之后?显然&lt;T&gt; 跟在operator== 后面是必须的,但为什么呢?

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

    注意T 可以省略,但&lt;&gt; 仍然需要。如:

    // 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&lt;&lt;T&gt; 附近产生编译错误

    如你所说,应该写成friend bool operator&lt; &lt;T&gt;(...),或者friend bool operator&lt; &lt;&gt;(...),或者看我关于非模板函数朋友的建议。

    【讨论】:

      【解决方案3】:

      第一个问题:一行

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

      为什么&lt;T&gt; 出现在== 之后?为什么不简单地写

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

      如果你删除了&lt;T&gt;,gcc 会给出警告:

      警告:朋友声明 'bool operator==(const Blob&lt; &lt;template-parameter-1-1&gt; &gt;&amp;, const Blob&lt; &lt;template-parameter-1-1&gt; &gt;&amp;)' 声明了一个非模板函数 [-Wnon-template-friend]

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

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

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

      如果您不添加&lt;T&gt;(或&lt;&gt;friend 声明),则必须为每种类型定义一个函数,这可能不是您想要的。

      第二个问题:如果我想为同一个类定义关系小于运算符

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

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2011-04-28
        • 1970-01-01
        • 2011-05-08
        • 2023-03-11
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多