友元声明是声明一个非模板函数。

61

我有一个类似下面代码的基类。我尝试重载 << 以便在 cout 中使用。 然而,g++ 报错:

base.h:24: warning: friend declaration ‘std::ostream& operator<<(std::ostream&, Base<T>*)’ declares a non-template function
base.h:24: warning: (if this is not what you intended, make sure the function template has already been declared and add <> after the function name here) -Wno-non-template-friend disables this warning

我尝试在类声明/原型中的<<后面添加<>;>。但是,这时我收到了未匹配任何模板声明的错误信息。我一直试图将运算符定义完全模板化(我希望如此),但我只能使用以下代码使其工作,并手动实例化运算符。

base.h

template <typename T>
class Base {
  public:
    friend ostream& operator << (ostream &out, Base<T> *e);
};

base.cpp

ostream& operator<< (ostream &out, Base<int> *e) {
    out << e->data;
return out;
}

我想在头文件base.h中只是有这个或类似的内容:

template <typename T>
class Base {
  public:
    friend ostream& operator << (ostream &out, Base<T> *e);
};

template <typename T>
ostream& operator<< (ostream &out, Base<T> *e) {
    out << e->data;
return out;
}

我在其他网站上看到说在原型中在 << 和 () 之间添加 <> 可以解决这个问题,但实际上并没有。是否可以将其放入单个函数模板中?


4
这正是丹·萨克斯的“交朋友”习语所解决的问题。 (抱歉评论晚了。) - gx_
我已经链接到另一个带有答案的问题,详细解释了为什么建议的修复措施是必要的/有效的。 - M.M
6个回答

42

听起来你想要改变:

friend ostream& operator << (ostream& out, const Base<T>& e);

收件人:

template<class T>
friend ostream& operator << (ostream& out, const Base<T>& e);

29
我不相信你,但这确实有效。然而,我不能使用T,因为它会掩盖类模板中已经存在的T - mike_b
3
GCC 是否会警告你有关变量遮蔽的问题? - asdfjklqwer
12
是的,GCC提示出现了变量遮蔽警告。我将"T"替换为"Y"解决了这个问题。error: shadows template parm ‘class T’ - mike_b
3
有趣。我用 Visual Studio 测试了我的答案,它对阴影没有问题。但我可以理解为什么 GCC 可能会发出警告……就像你说的那样,用另一个模板参数替换 T 将消除阴影。顺便说一下,Ben,你的解决方案在 MSVC 中无法编译。 - asdfjklqwer
我的代码也被Comeau拒绝了。这真的很烦人,因为它完全符合OP想要的语法,但显然违反了友元函数的限制。给你点赞。 - Ben Voigt
14
即使S和T不同,operator<<(ostream&, const Base<T>&)也会成为Base<S>的友元吗?我认为答案应该澄清这一点。 - Tom Artiom Fiodorov

25
Gcc是正确的警告你。尽管它看起来像是一个函数模板(它带有Base参数),但它实际上不是一个函数模板。
你的类定义中有一个非模板声明的友元函数(没有模板),但后面的友元函数定义是一个函数模板(即以template开头)。
此外,你的operator<<接受一个Base*,这是不正确的。它应该是Base const &,以保留其内置语义。
可能你想要的是下面这样的东西:
template <typename T> 
class Base { 
  public: 
    friend ostream& operator << (ostream &out, Base<T> const &e){
       return out;
    }; 
}; 

int main(){
   Base<int> b;
   cout << b;
}

如果您需要完全模板化,则可能需要使用这个。但我不确定这比以前的实现更加有用。由于查找涉及到ADL,只要调用不是来自与此类相关的上下文(例如来自“main”函数),您将永远无法解析到T不等于U的任何情况。
template <typename T>  
class Base {  
  public:  
    template<class U> friend ostream& operator << (ostream &out, Base<U> const &e){ 
       return out; 
    };  
};

int main(){ 
   Base<int> b; 
   cout << b; 
} 

使用<<运算符永远不会找到任何U != T的实例化。直接调用可能会,但这只是非常奇怪的情况。只有当Base<U>operator <<访问类型为Base<T>的全局变量时才会有影响。 - Ben Voigt
@Ben Voigt:添加了“只要调用不来自与该类相关的上下文,例如'main'函数”。希望现在更清晰了。 - Chubsdad
也许为了完整性,可以添加如何在类定义之外定义友元函数的内容? - Gauthier
@Chubsdad 问题是你不能使用两个不同的模板参数来实例化 Base,比如 Base<int> b; Base<float> c;,因为这样会导致 template<class U> ostream& operator<<(ostream&, Base<U> const &) 有多个定义(请参见此处:https://godbolt.org/z/oxK59qcMW)。 - Artyer

9
也许你正在寻找的是:
template <typename T>
class Base;

template <typename T>
ostream& operator<< (ostream &, const Base<T>&);

template <typename T>
class Base
{
  public:
    template<>
    friend ostream& operator << <T>(ostream &, const Base<T> &);
};

template <typename T>
ostream& operator<< ( ostream &out, const Base<T>& e )
{
    return out << e->data;
}

这里的“friends”只是模板的单个实例,其中运算符的模板参数与类的模板参数相匹配。

更新:不幸的是,这是非法的。MSVC和Comeau都拒绝了它。这就引出了一个问题,为什么原始的错误消息几乎完全建议采用这种方法。


6
C++ FAQ 中提到,类声明中的 friend 行应该有一个 <>,而不是 <T> - Michael Kristofik
2
显然,在此之前你不需要template<>这一行。(现在无法访问编译器,尚未尝试。) - Michael Kristofik
我认为原始错误信息建议使用public: friend ostream& operator << <>(ostream &, const Base<T> &); - M.M

7
改变
friend ostream& operator << (ostream& out, const Base<T>& e);

为了

friend ostream& operator << <T>(ostream& out, const Base<T>& e);

应该也可以——我刚刚用这种方法解决了一个完全相同的问题。


-1

改变

friend ostream& operator << (ostream &out, Base<T> *e)`

template<T> friend ostream& operator << (ostream &out, Base *e)

这只是一个虚假的解决方案。template <T> 实际上是 template <T unnamed>,其中声明了一个未命名的非类型参数。这使得对此函数的调用失败,因为未提供模板参数,因此查找将选择全局版本的 operator<<。但这并没有将全局版本声明为 Base 的友元,因此在该函数中访问 Base 的私有成员是不允许的。 - GKxx
事实上,这与删除该友元声明具有相同的效果。 - GKxx

-2

改变

ostream& operator<< (ostream &out, Base<int> *e) {
    out << e->data;
    return out;
}

ostream& operator<< (ostream &out, T *e) {
    out << e->data;
    return out;
}

2
这肯定行不通。即使在.h或.cpp文件中将其变成模板,仍会出现“employee.cpp:32: error: ‘T’ was not declared in this scope”的错误,并且由于链接期间出现未定义的符号而无法工作。此外,我仍然收到警告。我希望它像头文件中定义的普通模板一样。 - mike_b
我完全错过了基础课程。应该只是T,但我看到更完整的答案,所以我会给它们加分。 - Bruce Armstrong

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接