9

この質問で説明されている問題 (テンプレート関数をテンプレート クラスのフレンドとして宣言する) に苦労していましたが、2 番目の答えは私がやりたいことだと思います (テンプレート関数を前方宣言し、特殊化をフレンドとして指定します)。 )。わずかに異なる解決策が実際に正しいのか、それとも 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
  }

まず、私が見つけた奇妙なことの 1 つは、の前方宣言を変更してoperator<<一致しないようにすると (たとえば、std::ostream& operator<<(std::ostream &out, int fake);すべてが正しくコンパイルされ、正しく動作することでした (明確にするために、そのような関数を定義する必要はありません。ただし、リンク先の質問のように、前方宣言を削除すると、コンパイラがフレンド関数ではなくデータ メンバーを宣言していると考えているように見えるため、問題が発生します。 Visual C++ 2008 のバグ。

興味深いのは、上記のコードで forward 宣言を削除し、代替の friend 宣言を使用する場合です。テンプレート パラメーターUは、次の署名には表示されないことに注意してください。このメソッドもコンパイルされ、正しく動作します (他に何も変更する必要はありません)。私の質問は、これが標準に準拠しているのか、それとも Visual C++ 2008 の特異性に準拠しているのかということです (参考書で適切な答えを見つけることができませんでした)。

フレンド宣言template <typename U> friend ... const test<U> &t);も機能しますが、実際にはこれにより、オペレーターの各インスタンスが のfriend任意のインスタンスにアクセスできるようになりますがtest、私が望むのは、 のプライベート メンバーにtest<T>は からのみアクセスできるようにすることoperator<< <T>です。test<int>内部でをインスタンス化しoperator<<、プライベート メンバーにアクセスして、これをテストしました。を出力しようとすると、コンパイル エラーが発生するはずtest<double>です。

概要: 上記のコードで forward 宣言を削除して代替の friend 宣言に切り替えると、(Visual C++ 2008 の場合) 同じ結果になるようです -- このコードは実際に正しいのでしょうか?

更新: コードに対する上記の変更はいずれも gcc では機能しないため、これらは Visual C++ コンパイラのエラーまたは「機能」であると推測しています。それでも、標準に精通している人々からの洞察をいただければ幸いです。

4

1 に答える 1

7

...演算子<<の前方宣言を変更して一致しない場合

フレンド関数は、非常に特殊なタイプの宣言と見なす必要があります。本質的に、コンパイラは宣言を解析するのに十分ですが、実際にクラスを特殊化しない限り、セマンティックチェックは行われません。

提案された変更を行った後、インスタンス化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);
}

...このコードは実際に正しいですか?..

これには2つの部分があります。1つ目は、代替フレンド関数がスコープの後半の宣言を参照しないことです。

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!

フレンド関数は、実際には各スペシャライゼーションの名前空間で宣言されます。

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回答に従ってインライン関数をインラインで定義します(これはおそらく私が行うことです)。

更新:2番目のコメント

...クラスの前に前方宣言を変更します。クラス内のものは、後で実装する関数とまだ一致しています...

上で指摘したように、コンパイラーは、宣言にが含まれoperator<<ている場合、がテンプレートであるかどうかをチェックします。<

friend std::ostream& operator<<  <

これは、名前を検索し、それがテンプレートであるかどうかを確認することによって行われます。ダミーの前方宣言がある限り、これはコンパイラーを「だまして」友達をテンプレート名として扱うようにします。したがって、<はテンプレート引数リストの開始と見なされます。

後でクラスをインスタンス化すると、一致する有効なテンプレートがあります。本質的には、コンパイラをだまして友人をテンプレートの特殊化として扱うように仕向けているだけです。

(前に述べたように)この時点ではセマンティックチェックが行われないため、ここでこれを行うことができます。

于 2009-11-24T12:02:58.697 に答える