• ベストアンサー

テンプレートクラス中のフレンドクラス

下記をg++(fedora core1)でコンパイルしたところ、 #include <iostream> using namespace std; template< typename T > class A {   T a; public:   A(T aa ) : a(aa) { }   friend ostream& operator<<( ostream &os, const A &a ); //9行目 }; template< typename T > ostream& operator<<( ostream &os, const A<T> &a ) { return os << a.a; } int main( ) {   A<int>  a(5);   cout << a << '\n';   return 0; } 9行目にこのような警告・エラーが出てコンパイルできませんでした。(下記のオプションも試してみましたがダメでした) friend declaration 'std::ostream& operator<<(std::ostream&, const A<T>&)' declares a non-template function (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. :undefined reference to 'operator<<(std::basic_ostream <char, std::char_traits<char> >&, A<int> const&)' なぜ、コンパイルできないのかが分かりません。ちなみに、bcc32(borland c++ compiler5.5.1)では同様のエラーが出てコンパイルできず、cl(VC++6.0)ではコンパイル・実行可能でした。 ご存知の方いらっしゃったらご教授お願いします。(bccとclはWinXPです)

質問者が選んだベストアンサー

  • ベストアンサー
  • nyan5504
  • ベストアンサー率42% (6/14)
回答No.2

リンクエラーになるのは、テンプレート関数のインスタンス化ができないからです。テンプレート関数は、それが使用されているいずれかの場所において、その定義も行われていなければなりません。 これは通常の関数が定義と同時にコードが生成されるのと異なり、テンプレート関数はT<int>などと実際にテンプレート引数が決まったときにしかコードが生成できないためです。この点について、テンプレートはインライン関数と似たような性質を持っています。 この制約のため、テンプレート関数は宣言と定義が併記されることが非常に多いです。STLなどもそうです。 これを回避する方法として、"明示的な実体化"という手法があります。これはテンプレート引数を決定したテンプレート関数の実体を予め生成してしまう手段です。実体がコードとして存在するようになるため、リンクエラーにならなくなります。詳しくは調べてください。

kary
質問者

お礼

ありがとうございます。フレンドクラスやテンプレート等のそれぞれについては手元の書籍を読み進めましたが、これらを混ぜて応用しようとするとうまくできません。さらに勉強したいと思います。

その他の回答 (1)

  • mitoneko
  • ベストアンサー率58% (469/798)
回答No.1

friend ostream& operator<<( ostream &os, const A<T> &a ); //9行目  としておかないと、構文上は確かに、エラーですね。テンプレートクラスのフレンド関数は、暗黙のテンプレート関数とはなりません。(これに対して、メンバー関数は、暗黙のうちに、そのクラスのテンプレート引数を持つテンプレート関数となります。)  ですから、Aという型は存在しないということになりますので、A<T>と宣言してやる必要があります。  ところが、bcc32ですと、こうしても、まだエラーになります。今度は、リンクエラーで、このテンプレート関数を認識してくれないのです。実験してみた限りでは、おそらく、ostreamがらみのtypedefがいたずらしていると思われますが、そこまでは解析しきれませんでした。  回避方法は、public関数として、printなりなんなりのメンバー関数を定義して、これを外部の非フレンドの大域テンプレート関数から呼び出す形にしてあげるとなぜかすべて順調にいきます。

kary
質問者

お礼

ありがとうございます。ご回答を参考にしましていろいろ試したところ、 template<typename T> class A {   /* ... */ public:   friend void print_classA( const A<T> &a ); //{ cout << a.a; } <-- 定義もする }; template< typename T > void print_classA( const A<T> &a ) { cout << a.a; } としてもエラーになりますが、コメントのように宣言と定義を同じ場所でするとコンパイル・実行ともに可能でした。テンプレートクラスのフレンド関数は、宣言と定義を別の場所にすると互いを結びつけることができないとも思えますが、手元の書籍を調べてもはっきりとは分かりませんでした。