【问题标题】:Class Templates and Friendship in C++C++ 中的类模板和友谊
【发布时间】:2021-10-30 03:43:27
【问题描述】:

我正在尝试了解模板和友谊在 C++ 中是如何工作的。所以我自己看/尝试了一些例子。下面给出了一个我无法理解的示例:

版本 1

#include <iostream>

using namespace std;


//template<typename T> void func4();
//template<typename T> class NAME;
//   template<typename T> std::ostream& operator<< (std::ostream&, NAME<T> const&);
template<typename T>
class NAME {
   

    friend void func4<T>();
    friend std::ostream& operator<< <T> (std::ostream&, NAME<T> const&);
};
int main()
{
   cout << "Hello World" << endl; 
 
   return 0;
}

以上版本1报如下错误:

prog.cc:13:17: error: variable or field 'func4' declared void
   13 |     friend void func4<T>();
      |                 ^~~~~
prog.cc:13:17: error: expected ';' at end of member declaration
   13 |     friend void func4<T>();
      |                 ^~~~~
      |                      ;
prog.cc:13:22: error: expected unqualified-id before '<' token
   13 |     friend void func4<T>();
      |                      ^

我的第一个问题是,即使我已经评论了func4operator&lt;&lt; 模板函数的前向声明,那么我怎么才能只得到func4 的错误?这就是operator&lt;&lt; 没有错误的原因。

请注意,如果我们想与模板的特定实例成为朋友,我知道我们需要前向声明。那是为了

friend void func4<T>();

为了工作,我们需要注释掉语句

template<typename T> void func4();

对于

friend std::ostream& operator<< <T> (std::ostream&, NAME<T> const&);

为了工作,我们需要在程序开始时注释掉相应的前向声明。但是当我只注释掉template&lt;typename T&gt; void func4(); 语句时,程序可以运行并且operator&lt;&lt; 没有错误。同样为什么我们没有收到operator&lt;&lt; 的错误,即使我没有注释掉相应的前向声明。

我的第二个问题是这样的陈述

friend void func4<T>();

friend void func4<>();

同样是语句

friend std::ostream& operator<< <T> (std::ostream&, NAME<T> const&);

friend std::ostream& operator<< <> (std::ostream&, NAME<T> const&);

我的想法是语句friend void func4&lt;&gt;(); 是使用语句template &lt;typename T&gt; void func4(); 声明的函数模板的特化。同时,当我们编写friend void func4&lt;T&gt;(); 时,我们显式 传递了T,所以在这种情况下,当我们编写/调用func4&lt;int&gt;(); 时,函数模板func4&lt;int&gt; 将被实例化,然后占用内存。另一方面,专门的模板函数已经占用了一些内存,因为我们必须在调用它之前提供它的定义。那么有人可以解释一下我们使用&lt;T&gt; 和使用&lt;&gt; 时是否存在区别。总之,在我看来,在&lt;&gt; 的情况下,将使用用户提供的专业化,而在&lt;T&gt; 的情况下,当我们调用func4() 时将创建一个新的intiantion。所以存在差异,因为在&lt;&gt; 的情况下,我们必须提供一个已经占用一些空间(内存)的定义,而在&lt;T&gt; 的情况下,func4 只会在我们使用/调用它时占用空间(内存)。这是正确的结论吗?

我的第三个问题是我有read

无法为重载运算符、转换函数和构造函数显式指定模板参数,因为它们在调用时不使用函数名。

根据上面引用的语句,我们不能显式地为重载运算符指定模板参数,但是如何将friend std::ostream&amp; operator&lt;&lt; &lt;T&gt; (std::ostream&amp;, NAME&lt;T&gt; const&amp;);写成重载运算符

【问题讨论】:

  • 请格式化一下,文字墙很难阅读。
  • @HatteRooster 好的,我已经格式化了问题的第一部分和第二部分。你可以再检查一下。
  • 第三个问题中的引用谈论的是使用运算符,而不是定义它。您不能通过编写 x &lt;&lt; &lt;some_type&gt; z;(或您可能想到的任何其他形式)之类的内容来指定模板参数。
  • 您是否已经得到了所有问题的答案?

标签: c++ c++11 templates lookup argument-dependent-lookup


【解决方案1】:

回答 1 operator&lt;&lt; 没有错误,因为您使用了 using namespace std; 并且在 std 命名空间中已经重载了 operator&lt;&lt;

答案 3 引用的语句谈到调用(即使用)重载运算符而不是定义/声明它们

答案 2

版本 1

#include <iostream>

template<typename T> void func4();
template<typename T>
class NAME {
   

    //friend void func4<>();//this does not work because there is no way to deduce the template arguments
    friend void func4<T>();//this works because here we have explicitly passed T as the template argument

};
int main()
{
   std::cout << "Hello World" << std::endl; 
   NAME<int> n;
   return 0;
}

在上面的版本1中,我们有一个函数模板func4。现在,当您编写 friend void func&lt;T&gt;();friend void func&lt;&gt;(); 时,您正在成为类模板 NAME 的完全专业化朋友。 friend void func&lt;&gt;(); 不起作用的原因是在这种情况下模板参数推导无法工作。当你写 friend void func&lt;T&gt;(); 时,你已经明确地传递了模板参数,所以这是可行的。这也意味着两者(friend void func&lt;T&gt;friend void func&lt;&gt;();)本质上是模板函数 func 的相同特化。这在我的下一个示例版本 2 中会更清楚。

第 2 版

#include <iostream>

template<typename T> class NAME;
template<typename T> std::ostream& operator<<(std::ostream&, const NAME<T>& );
template<typename T>
class NAME {
   
    //both of the below friend declarations are equivalent
    friend std::ostream& operator<< <T>(std::ostream&, const NAME<T>&);//this works 
    friend std::ostream& operator<< <>(std::ostream&, const NAME<T>&);//this works as well becasue of template argument deduction
    
};
int main()
{
   std::cout << "Hello World" << std::endl; 
   NAME<int> n;
   return 0;
}

在上面的版本 2 中,我们有一个重载的运算符函数模板。 friend std::ostream&amp; operator&lt;&lt; &lt;T&gt;(std::ostream&amp;, const NAME&lt;T&gt;&amp;);friend std::ostream&amp; operator&lt;&lt; &lt;&gt;(std::ostream&amp;, const NAME&lt;T&gt;&amp;); 都是等价的 并且是重载模板operator&lt;&lt; &lt;T&gt; 的相同特化。它们之间唯一的区别是第二个使用模板参数推导。这是可能的,因为与版本 1 不同,我们有一个函数参数,这次依赖于 T,因此模板参数推导可以工作。这在函数模板 func4&lt;&gt; 的情况下是不可能的,因为该函数不采用任何依赖于模板参数的参数。

【讨论】:

  • 答案 1 是……至少不完整。您可以删除using namespace std;,代码仍将在GCC 和MSVC 中编译(或者您可以将运算符更改为operator+)。看起来这些编译器对友元运算符模板特化声明非常宽松,这是一个错误; Clang 在所有情况下都正确拒绝了代码。请注意,即使在其中包含 using namespace std;,Clang 也会正确拒绝代码,因为即使在 std 中确实有 operator&lt;&lt; 的声明,朋友声明也不对应其中任何一个(推导失败)。
  • @bogdan 如果我们删除using namespace std;,代码不会编译,如here 所示。在同一个程序中(我在这个评论中给出了它的链接)如果你添加using namespace std; 那么它会编译。
  • 你说得对,using namespace std;-std=c++11 影响 GCC 中的编译(最多 c++17);我在 C++20 模式下编译,没有测试其他的。即使在先前使用using namespace std; 的GCC 标准模式下,如果您尝试实例化模板(将NAME&lt;int&gt; n; 添加到main),代码也将无法编译。这是我要评论的重要部分:即使 std 中有 operator&lt;&lt; 的声明,但朋友声明与其中任何一个都不匹配,因此程序仍然是不正确的,即使使用在里面使用指令。
  • 想一想,无论是格式错误、不需要诊断还是需要诊断都是一个有趣的问题 :-),但它是格式错误的。
猜你喜欢
  • 2021-12-06
  • 2014-09-30
  • 2012-09-29
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多