1. ホーム
  2. c++

テンプレートクラスでフレンド演算子<<をオーバーロードする

2023-08-07 13:37:26

質問

私は今StackOverflow.comで私の問題についてのいくつかの質問を読みましたが、そのうちのどれも私の問題を解決していないようです。または私はそれを間違っていたかもしれません... オーバーロードされた << は、私がそれをインライン関数にした場合、動作します。でも、私の場合はどうすれば動くのでしょうか?

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

コードです。

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;
};


int main()
{

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

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

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

どのように解決するのですか?

これはよくある質問の1つで、似ているようで実は同じではない、さまざまなアプローチがあります。3 つのアプローチは、関数の友人であることを誰に宣言するか、そしてそれをどのように実装するかが異なります。

外向的なアプローチ

テンプレートのすべてのインスタンスを友人として宣言します。これは、あなたが回答として受け入れたものであり、また、他の回答のほとんどが提案しているものです。このアプローチでは、特定のインスタンスを不必要に開くことになります。 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>& ) .

これは2つの方法で行うことができます。簡単な方法は @Emery Berger が提案したように、演算子をインライン化することです --これは他の理由からも良いアイデアです。

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<< のインスタンス化に対してテンプレート化されていない関数を作成するのではありません。 Test テンプレートを使用します。繰り返しますが、違いは微妙ですが、これは基本的に手動で追加するのと同じです。 std::ostream& operator<<( std::ostream&, const Test<int>& ) をインスタンス化するときに Test<int> をインスタンス化するとき、また別の同様のオーバーロードをするとき Testdouble または他の型と

3番目のバージョンはより面倒です。コードをインライン化せず、テンプレートを使用することで、テンプレートの単一のインスタンス化をクラスの友人として宣言することができます。 すべて 他のインスタンス化に対して自分自身を開くことなく、テンプレートの単一のインスタンスをあなたのクラスの友人と宣言することができます。

// 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> ...
}

外向的な性格を利用する

この3つ目の選択肢と1つ目の選択肢の微妙な違いは、他のクラスに対してどれだけオープンにしているかということにあります。の悪用例です。 外向型 バージョンでの悪用の例としては、あなたの内部へのアクセスを取得したい誰かがこれを実行することです。

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...
   }
}