【问题标题】:Template friend function of a template class模板类的模板友元函数
【发布时间】:2010-12-19 17:13:57
【问题描述】:

我正在努力解决this question 中描述的问题(将模板函数声明为模板类的朋友),我相信第二个答案是我想要做的(转发声明模板函数,然后命名专业化为朋友)。我有一个问题,一个稍微不同的解决方案实际上是正确的还是恰好在 Visual C++ 2008 中起作用。

测试代码是:

#include <iostream>

// forward declarations
template <typename T>
class test;

template <typename T>
std::ostream& operator<<(std::ostream &out, const test<T> &t);

template <typename T>
class test {
  friend std::ostream& operator<< <T>(std::ostream &out, const test<T> &t);
  // alternative friend declaration
  // template <typename U>
  // friend std::ostream& operator<<(std::ostream &out, const test<T> &t);

  // rest of class
  };

template <typename T>
std::ostream& operator<<(std::ostream &out, const test<T> &t) {
  // output function defined here
  }

首先,我发现一件奇怪的事情是,如果我更改 operator&lt;&lt; 的前向声明使其不匹配(例如,std::ostream&amp; operator&lt;&lt;(std::ostream &amp;out, int fake);,一切仍然编译并正常工作(要清楚,我不'不需要定义这样的函数,只需声明它)。但是,在链接到的问题中,删除前向声明会导致问题,因为编译器似乎认为我声明的是数据成员而不是友元函数。我很确定这种行为是 Visual C++ 2008 的错误。

有趣的是,当我在上面的代码中删除前向声明并使用替代朋友声明时。请注意,模板参数U 不会出现在以下签名中。此方法也可以正确编译和工作(无需更改任何其他内容)。我的问题是这符合标准还是 Visual C++ 2008 的特质(我在参考书中找不到好的答案)。

请注意,虽然朋友声明 template &lt;typename U&gt; friend ... const test&lt;U&gt; &amp;t); 也有效,但这实际上使运算符 friend 的每个实例都可以访问 test 的任何实例,而我想要的是 test&lt;T&gt; 的私有成员应该只可从operator&lt;&lt; &lt;T&gt; 访问。我通过在operator&lt;&lt; 中实例化test&lt;int&gt; 并访问私有成员来对此进行了测试;当我尝试输出 test&lt;double&gt; 时,这应该会导致编译错误。

概要:在上面的代码中删除前向声明并切换到替代朋友声明似乎会产生相同的结果(在 Visual C++ 2008 中)——这段代码真的正确吗?

更新:以上对代码的任何修改在 gcc 下都不起作用,所以我猜测这些是 Visual C++ 编译器中的错误或“功能”。不过,我仍会感谢熟悉该标准的人提供的见解。

【问题讨论】:

  • 顺便说一句,我发现在类声明之前添加using namespace std; 将消除对前向声明的需要,我猜这是(可能的)编译器错误的副作用。
  • 根据我的评论-您可以明确添加您所说的示例(包括实例化)吗?口头上很难理解您对每个示例的含义。

标签: c++ visual-c++ templates friend-function


【解决方案1】:

...如果我更改 operator

友元函数应该被视为一种非常特殊的声明类型。本质上,编译器已经做了足够的工作来解析声明,但是除非您真正专门化该类,否则不会进行语义检查。

在进行建议的修改后,如果您随后实例化 test,您将收到关于声明不匹配的错误:

template class test<int>;

...但是...删除前向声明会导致问题

编译器尝试解析声明以存储它,直到类模板被专门化。在解析期间,编译器到达声明中的&lt;

friend std::ostream& operator<<  <

operator&lt;&lt; 后面跟 &lt; 的唯一方法是它是否是模板,因此会进行查找以检查它是否是模板。如果找到一个函数模板,那么&lt; 被认为是模板参数的开始。

当您删除前向声明时,找不到模板并且operator&lt;&lt; 被认为是一个对象。 (这也是为什么当您添加using namespace std 时代码会继续编译,因为必须有operator&lt;&lt; 的模板声明)。

...当我在上面的代码中删除前向声明并使用替代朋友声明时。请注意,模板参数 U 不会出现在以下签名中...

没有要求在函数模板的参数中使用所有模板参数。另一种声明是针对一个新的函数模板,该模板只有在命名空间中声明并指定显式模板参数时才可调用。

一个简单的例子是:

class A {};
template <typename T> A & operator<<(A &, int);

void foo () {
  A a;
  operator<< <int> (a, 10);
}

...这段代码真的正确吗?..

这有两个部分。首先是替代友元函数不引用作用域后面的声明:

template <typename T>
class test {
  template <typename U> 
  friend std::ostream& operator<<(std::ostream &out, const test<T> &t);
  };

template <typename T> 
std::ostream& operator<<(std::ostream &out, const test<T> &t);  // NOT FRIEND!

friend 函数实际上会在每个特化的命名空间中声明:

template <typename U> 
std::ostream& operator<<(std::ostream &out, const test<int> &t);
template <typename U> 
std::ostream& operator<<(std::ostream &out, const test<char> &t);
template <typename U>
std::ostream& operator<<(std::ostream &out, const test<float> &t);

operator&lt;&lt; &lt;U&gt; 的每个特化都可以根据其参数 test&lt;T&gt; 的类型访问特定的特化。因此,从本质上讲,访问是根据您的需要进行限制的。但是正如我之前提到的,这些函数基本上不能用作运算符,因为您必须使用函数调用语法:

int main ()
{
  test<int> t;
  operator<< <int> (std << cout, t);
  operator<< <float> (std << cout, t);
  operator<< <char> (std << cout, t);
}

根据上一个问题的答案,您可以使用litb 建议的前向声明,或者根据Dr_Asik's 答案内联定义朋友函数(这可能是我会做的) .

更新:第二条评论

...更改类前的前向声明;类中的那个仍然匹配我稍后实现的功能...

正如我上面指出的,当编译器在声明中看到&lt; 时,它会检查operator&lt;&lt; 是否为模板:

friend std::ostream& operator<<  <

它通过查找名称并检查它是否是模板来做到这一点。只要您有一个虚拟的前向声明,那么这就会“欺骗”编译器将您的朋友视为模板名称,因此 &lt; 被认为是模板参数列表的开始。

稍后,当您实例化该类时,您确实有一个有效的模板可以匹配。本质上,您只是在欺骗编译器将朋友视为模板特化。

您可以在此处执行此操作,因为(正如我之前所说),此时不会进行语义检查。

【讨论】:

  • 最初我认为它像你描述的那样工作,但事实并非如此(这就是我问这个问题的原因)。当我说“一切都编译并正常工作”时,我实际上的意思是我能够成功地实例化该类并以通常的方式使用&lt;&lt; 运算符。我还检查了操作符函数是否具有正确的访问权限(例如,如果调用它来打印test&lt;double&gt;,它就无法访问test&lt;int&gt; 的私有成员。编写的代码就是这种情况,带有“错误" 前向声明,以及可选的朋友声明。
  • 另外我意识到你可能误解了我所说的“假”声明——我只是说在上课前改变前向声明;类中的那个仍然匹配我稍后实现的函数(因此在实例化时,所需的函数模板已经被解析)。
  • @Sumudu:我添加了一个更新来解决您的第二条评论。至于第一个,您能否修改您的问题以包含您说“编译并正常工作”但使用替代朋友功能模板的示例?当我在这里这样做时,我在访问命名空间模板中的成员时遇到错误。
猜你喜欢
  • 1970-01-01
  • 2016-10-19
  • 2013-09-18
  • 2011-07-15
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多