重载友元运算符<<用于类模板

Overloading friend operator<< for class template

提问人:starcorn 提问时间:1/12/2011 最后编辑:Jan Schultkestarcorn 更新时间:9/7/2023 访问量:61476

问:

如果我将其变成内联函数,则重载有效。但是我如何让它在我的情况下工作呢?<<

#include <iostream>
using namespace std;

template <class T>
T my_max(T a, T b) {
   if(a > b) return a;
   else      return b;
}

template <class classT>
class D {
public:
   D(classT in) : d(in) {};
   bool operator>(const D& rhs) const;
   classT operator=(const D<classT>& rhs);

   friend ostream& operator<<(ostream &os, const D<classT>& rhs);
private:
   classT d;
};

template <class classT>
ostream& operator<<(ostream &os, const D<classT>& rhs) {
   os << rhs.d;
   return os;
}

int main() {
   D<int> d1(1);
   D<int> d2(2);
   cout << my_max(d1, d2) << endl;
}

这无法编译:

warning: friend declaration std::ostream& operator<<(std::ostream&, const D<classT>&)' declares a non-template function

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

/tmp/cc6VTWdv.o:uppgift4.cc:(.text+0x180): undefined reference to operator<<(std::basic_ostream<char, std::char_traits<char> >&, D<int> const&)' collect2: ld returned 1 exit status

C++ 模板 运算符重载 ostream

评论

0赞 Daniel Trebbien 1/12/2011
最近有一个关于这个问题的问题,可能很有启发性:stackoverflow.com/questions/4571611/virtual-operator
0赞 starcorn 1/12/2011
@Daniel - 它没有解决我在模板类重载时遇到的问题
9赞 David Rodríguez - dribeas 1/12/2011
我认为最好不要用给定的答案修改问题。这使得确定原始问题是什么变得更加困难。您可能希望在最后添加一个 EDIT,其中包含解决问题的更改,但是当问题随着时间的推移而变化时,我发现它令人困惑,我必须调出历史记录以查看最初实际被问到的问题。

答:

16赞 Nim 1/12/2011 #1

你不能像这样声明一个朋友,你需要为它指定一个不同的模板类型。

template <typename SclassT>
friend ostream& operator<< (ostream & os, const D<SclassT>& rhs);

注意,这样它就不会遮蔽。定义SclassTclassT

template <typename SclassT>
ostream& operator<< (ostream & os, const D<SclassT>& rhs)
{
  // body..
}

评论

0赞 starcorn 1/12/2011
谢谢这工作用这个代码编辑了我的问题,我会在股票代码下降后立即勾选此答案。
4赞 David Rodríguez - dribeas 1/12/2011
请注意,这并不是将 的特定实例化声明为友元,而是将所有实例化声明为友元,包括模板的任何专用化。在这里看到答案operator<<
0赞 Rupesh Yadav. 4/8/2016
@starcorn你应该改变你选择的答案,以提供更好的答案,这应该是大卫罗德里格斯的答案。
0赞 starcorn 4/8/2016
@Nim根据用户意见,我更改了问题的所选答案。但是,感谢您当时对我的帮助:)
0赞 Nim 4/11/2016
@starcorn,不用担心,我当时也投票支持大卫的答案,这是对问题的更全面的处理。
4赞 EmeryBerger 1/12/2011 #2

这对我有用,没有任何编译器警告。

#include <iostream>
using namespace std;

template <class T>
T my_max(T a, T b)
{
  if(a > b)
    return a;
  else
    return b;
}

template <class classT>
class D
{
public:
  D(classT in)
    : d(in) {};

  bool operator>(const D& rhs) const {
    return (d > rhs.d);
  }

  classT operator=(const D<classT>& rhs);

  friend ostream& operator<< (ostream & os, const D& rhs) {
    os << rhs.d;
    return os;
  }

private:
  classT d;
};


int main()
{

  int i1 = 1;
  int i2 = 2;
  D<int> d1(i1);
  D<int> d2(i2);

  cout << my_max(d1,d2) << endl;
  return 0;
}

评论

0赞 starcorn 1/12/2011
是的,我已经这样做了,但是如果我不想将其作为内联函数怎么办?operator<<
0赞 Martin York 1/12/2011
@starcorn:方法/函数是否被内联(隐式或显式)标记,与函数实际上在代码中内联几乎没有关系。因此,这是一种毫无意义的担忧。
0赞 David Rodríguez - dribeas 1/12/2011
+1.@starcorn:这个解决方案比公认的要好。差异是微妙的,但是在公认的答案中,您将 的所有实例化(和可能的专用化)声明为友元,而在此解决方案中,您仅授予对具有相同类型的实例化的访问权限。此外,作为在类内部定义的副作用,您将该参数的可见性限制为仅两个参数之一为 -- 的情况,除非其中一个参数是 ,否则编译器甚至不会考虑重载。operator<<operator<<operator<<operator<<Doperator<<D<T>
2赞 David Rodríguez - dribeas 1/12/2011
@starcorn:我在这里添加了一个答案,试图清除三种不同方法的差异
0赞 John Dibling 1/12/2011 #3

给你:

#include <cstdlib>
#include <iostream>
using namespace std;

template <class T>
T my_max(T a, T b)
{
   if(a > b)      
      return a;
   else
      return b;
}

template <class classT>
class D
{
public:
   D(classT in)
      : d(in) {};
   bool operator>(const D& rhs) const { return d > rhs.d;};
   classT operator=(const D<classT>& rhs);

   template<class classT> friend ostream& operator<< (ostream & os, const D<classT>& rhs);
private:
   classT d;
};

template<class classT> ostream& operator<<(ostream& os, class D<typename classT> const& rhs)
{
    os << rhs.d;
    return os;
}


int main()
{

   int i1 = 1;
   int i2 = 2;
   D<int> d1(i1);
   D<int> d2(i2);

   cout << my_max(d1,d2) << endl;
   return 0;
}

评论

0赞 Gene Bushuyev 1/12/2011
我认为这不应该编译:.参数声明中不允许使用详细说明类型,并且需要 qualified-id。template<class classT> ostream& operator<<(ostream& os, class D<typename classT> const& rhs)typename
0赞 John Dibling 1/12/2011
@Gene:嗯。它确实在关闭 MS 扩展的最高级别为我编译。
0赞 David Rodríguez - dribeas 1/12/2011
它不使用 g++ 编译,我相信这个编译器。中的第二个论点是,我认为这是不正确的。我会改用。关键字在那里是可选的(99.9% 的情况),但 的用法不是两个已知用途之一:它作为 的替代品是无效的,它用于标识模板上的依赖名称实际上是一种类型。operator<<class D<typename classT>D<classT>classtypenameclass
1赞 Alessandro Teruzzi 1/12/2011 #4

我认为你一开始就不应该交朋友。

您可以创建一个公共方法调用 print,如下所示(对于非模板类):

std::ostream& MyClass::print(std::ostream& os) const
{
  os << "Private One" << privateOne_ << endl;
  os << "Private Two" << privateTwo_ << endl;
  os.flush();
  return os;
}

然后,在类之外(但在同一个命名空间中)

std::ostream& operator<<(std::ostream& os, const MyClass& myClass)
{
  return myClass.print(os);
}

我认为它应该也适用于模板类,但我还没有测试过。

210赞 David Rodríguez - dribeas 1/12/2011 #5

这是那些具有相似但并不完全相同的不同方法的常见问题之一。这三种方法的不同之处在于你声明谁是你的函数的朋友,然后是你如何实现它。

性格外向

将模板的所有实例声明为友元。这是你所接受的答案,也是大多数其他答案所提出的。在这种方法中,您不必要地通过声明好友所有实例化来打开您的特定实例化。也就是说,可以访问 的所有内部。D<T>operator<<std::ostream& operator<<( std::ostream &, const D<int>& )D<double>

template <typename T>
class Test {
   template <typename U>      // all instantiations of this template are my friends
   friend std::ostream& operator<<( std::ostream&, const Test<U>& );
};
template <typename T>
std::ostream& operator<<( std::ostream& o, const Test<T>& ) {
   // Can access all Test<int>, Test<double>... regardless of what T is
}

内向的人

仅将插入运算符的特定实例声明为友元。 当插入运算符应用于自身时,它可能喜欢,但它不希望与 .D<int>std::ostream& operator<<( std::ostream&, const D<double>& )

这可以通过两种方式完成,简单的方法是 Berger 提出的@Emery,即内联运算符——出于其他原因,这也是一个好主意:

template <typename T>
class Test {
   friend std::ostream& operator<<( std::ostream& o, const Test& t ) {
      // can access the enclosing Test. If T is int, it cannot access Test<double>
   }
};

在第一个版本中,您不是为模板的每个实例创建模板化函数,而是创建非模板化函数。同样,差异是微妙的,但这基本上等同于手动添加:当你实例化时,以及当你使用 或任何其他类型实例化时,另一个类似的重载。operator<<Teststd::ostream& operator<<( std::ostream&, const Test<int>& )Test<int>Testdouble

第三个版本比较麻烦。在不内联代码的情况下,通过使用模板,您可以将模板的单个实例声明为类的好友,而无需向所有其他实例开放:

// Forward declare both templates:
template <typename T> class Test;
template <typename T> std::ostream& operator<<( std::ostream&, const Test<T>& );

// Declare the actual templates:
template <typename T>
class Test {
   friend std::ostream& operator<< <T>( std::ostream&, const Test<T>& );
};
// Implement the operator
template <typename T>
std::ostream& operator<<( std::ostream& o, const Test<T>& t ) {
   // Can only access Test<T> for the same T as is instantiating, that is:
   // if T is int, this template cannot access Test<double>, Test<char> ...
}

利用外向

第三种选择与第一种选择之间的微妙区别在于您对其他课程的开放程度。外向版本中滥用的一个例子是有人想要进入你的内部并这样做:

namespace hacker {
   struct unique {}; // Create a new unique type to avoid breaking ODR
   template <> 
   std::ostream& operator<< <unique>( std::ostream&, const Test<unique>& )
   {
      // if Test<T> is an extrovert, I can access and modify *any* Test<T>!!!
      // if Test<T> is an introvert, then I can only mess up with Test<unique> 
      // which is just not so much fun...
   }
}

评论

1赞 dgrat 5/13/2016
在类外部实现函数会给出未定义的引用。在类中,一旦我用多个类型显式实例化宿主类,我就会遇到问题。
1赞 David Rodríguez - dribeas 5/13/2016
@dgrat:我无法调试我看不到的代码,但它确实有效。好友是否依赖于任何模板参数?如果不是,您将遇到多个定义的问题,因为不同的实例化将生成完全相同的函数(或者如果函数签名相同但主体不同,则违反 ODR)。
0赞 WhozCraig 8/16/2017
对于寻求与模板函数(不仅仅是运算符)成为朋友的人来说,这仍然是我更推荐的首选之一,这在很大程度上是由于我可以完成的方式存在明显差异,以及每种方法的好处/缺点。只是一个出色的答案,大卫。如果可以的话,我会把它提高十几倍。
0赞 wangdq 8/5/2018
在第三个版本中是“operator<< <T>”,这一行表示实例化运算符<<的函数模板作为类模板的好友,用相同的 T ?
0赞 WhozCraig 8/23/2018
@wangdq 是的,并且该专业化与模板专用化是友好的,并且与该专业化友好。也就是说,如果你有一些,唯一与之友好的就是实例化。有的任意,凡是同义词,不友;相反,它是朋友。这似乎没什么大不了的,但它可以在不知不觉中悄悄地成为一体。Test<T>operator <<operator << <T>Test<U>UToperator << <T>operator << <U>