繁体   English   中英

模板类的模板友好功能

[英]Template friend function of a template class

我正在努力解决这个问题中描述的问题 (将模板函数声明为模板类的朋友),我相信第二个答案是我想做的(转发声明模板函数,然后将专业化命名为朋友) )。 我有一个问题,一个稍微不同的解决方案是否实际上是正确的,或者只是恰好在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<<的前向声明,使它不匹配(例如, std::ostream& operator<<(std::ostream &out, int fake);仍然编译并正常工作(要清楚,我不需要定义这样的函数,只声明它。)但是,如在链接问题中,删除前向声明会导致问题,因为编译器似乎认为我我声明一个数据成员而不是友元函数。我很确定这个行为是一个Visual C ++ 2008错误。

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

注意,虽然朋友声明template <typename U> friend ... const test<U> &t); 也可以,这实际上给操作员friend每个实例访问任何test实例,而我想要的是test<T>的私有成员只能从operator<< <T> 我通过在operator<<实例化一个test<int>并访问一个私有成员来测试它; 当我尝试输出test<double>时,这会导致编译错误。

概要:删除前向声明并切换到上面代码中的替代友元声明似乎产生相同的结果(在Visual C ++ 2008中) - 这段代码实际上是否正确?

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

...如果我改变运算符<<的前向声明,使它不匹配

朋友功能应被视为一种非常特殊的声明。 本质上,编译器足以解析声明,但是除非您实际专门化类,否则不会进行语义检查。

在进行建议的修改之后,如果您再实例化test ,则会收到有关声明不匹配的错误:

template class test<int>;

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

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

friend std::ostream& operator<<  <

operator<<可以跟随<的唯一方法是,如果它是模板,则进行查找以检查它是否为模板。 如果找到函数模板,那么<被认为是模板参数的开始。

删除前向声明时,未找到模板,并且operator<<被视为对象。 (这也是为什么当你添加using namespace std代码继续编译,因为必须有operator<<的模板声明。

...当我删除前向声明并在上面的代码中使用替代的朋友声明时。 请注意,模板参数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<< <U>每个特化都可以根据其参数test<T>的类型访问特定的特化。 因此,实质上访问受限于您的要求。 但是正如我之前提到的,这些函数基本上不能用作运算符,因为你必须使用函数调用语法:

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

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

更新:第二次评论

......在课前改变前方声明; 类中的那个仍然匹配我稍后实现的功能...

正如我在上面指出的那样,编译器在看到声明中的<时检查operator<<是否是模板:

friend std::ostream& operator<<  <

它通过查找名称并检查它是否是模板来完成此操作。 只要您有一个虚拟前向声明,那么这会“欺骗”编译器将您的朋友视为模板名称,因此<被视为模板参数列表的开头。

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

你可以在这里这样做,因为(正如我之前所说),此时不会进行语义检查。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM