web-dev-qa-db-ja.com

名前空間と演算子の解決

グローバル名前空間で出力ストリーム演算子(operator <<)を定義するライブラリを使用しています。私自身の名前空間では、常にグローバル名前空間でそのような演算子を宣言しており、問題が発生することはありませんでした。しかし今、さまざまな理由で、これらの演算子を自分の名前空間で宣言する必要があり、突然、コンパイラーがライブラリーで宣言された演算子を見つけることができなくなったようです。

これが私の問題を説明する簡単な例です:

#include <iostream>

namespace A
{
   struct MyClass {};
}

std::ostream & operator<<( std::ostream & os, const A::MyClass & )
   { os << "namespace A"; return os; }

namespace B
{
   struct MyClass {};

   std::ostream & operator<<( std::ostream & os, const B::MyClass & )
      { os << "namespace B"; return os; }
}

namespace B
{
   void Test()
   {
      std::cout << A::MyClass() << std::endl;
      std::cout << B::MyClass() << std::endl;
   }
}

int main()
{
   B::Test();
   return 1;
}

次のエラーが発生します。

error: no match for ‘operator<<’ in ‘std::cout << A::MyClass()’

両方の演算子が名前空間内にある場合、または代わりに両方がグローバル名前空間にある場合、コードは正しくコンパイルおよび実行されることに注意してください。

何が起こっているのか、そしてそのような演算子を名前空間で定義するための「グッドプラクティス」が何であるのかを本当に理解したいと思います。

ありがとう!

41
chataign

Testは名前空間B内にあるため、コンパイルはその名前空間の演算子を確認し、一致する署名がないことに注意します。また、クラスを含む名前空間Aで演算子を見つけようとしますが、そこでも見つかりません。名前空間Bにはすでにこのような演算子(間違ったシグネチャを持つ)があるため、グローバルスコープで演算子を見つけようとはしません。

グローバルなものを検索しないのは、おおよそ以下の理由による。最初に標準を引用し、次にそれを説明しようと思います。

3.4/1から:

...名前ルックアップは、名前が関数名であることがわかった場合、複数の宣言を名前に関連付けることができます。宣言は、オーバーロードされた関数のセットを形成すると言われています(13.1)。オーバーロード解決(13.3)は、名前のルックアップが成功した後に行われます。

私がこれを読んでいると、コンパイラーが関数(オペレーターがこのコンテキストにある)を見つけようとすると、最初に名前検索を実行して関数を見つけようとします。次に、オーバーロードのセットから適切な関数を選択しようとします。

3.4.1/6から:

名前空間Nのメンバーである関数(26)の定義で使用される名前(ここで、説明の目的でのみ、Nはグローバルスコープを表すことができます)は、それが使用されるブロックで使用する前に宣言する必要がありますまたは、その囲みブロック(6.3)のいずれかで、または、名前空間Nで使用する前に宣言するか、Nがネストされた名前空間である場合は、Nの囲み名前空間の1つで使用する前に宣言する必要があります。

これを分解しましょう。名前空間レベルの関数でoperator<<を使用しているため、このセクションが適用されます。上記の優先順位を使用して、その演算子を見つけようとします。演算子は、現在のブロックまたはそれを囲むブロックで宣言されていません(これは、関数内のネストされた{}を参照しています)。ただし、次の部分は「...名前空間Nで使用する前に宣言する必要があります...」と一致します。 is実際には現在の名前空間(B)にoperator<<があるため、その演算子が一致リストに追加されます。 Bにはこれ以上一致するものがありません。同じ名前空間のスコープは可能な限り最も近い一致と見なされるため、他のスコープは調べません。

演算子を名前空間Aに配置すると機能する理由は、印刷されるアイテムがAのメンバーであるため、式の名前空間に含まれているため、実際にはその名前空間が考慮されるためです。名前空間Aisと見なされるため、その名前空間で適切な一致が検出され、正しくコンパイルされます。

可能な演算子のリストができたので、それらに対して過負荷解決を試みます。残念ながら、名前空間Bで見つかったものだけが考慮され、必要な引数と一致しません。

一般に、挿入演算子は、それが動作するクラスと同じ名前空間にある必要があります。

37
Mark B

私の答えは他の答えと非常によく似ていますが、特に、コンパイラはA :: operator <<()を見つけようとしています。名前空間の外部で呼び出す場合は、を使用して明示的に呼び出すことができます。

::operator<<(std::cout, A::MyClass();

構文をよりスムーズに使用するには、名前空間に配置します。

11
Lou

この問題は@MarkBの回答で説明されています。以下で問題を解決します。グローバルoperator<<を使用する名前空間で、次のコードを入力します。

using ::operator<<;

OPのコード例では、そのコード行は、operator<<namespace Bを宣言/定義する他のコードに沿った場所に移動します。

namespace B
{
   struct MyClass {};

   std::ostream & operator<<( std::ostream & os, const B::MyClass & )
      { os << "namespace B"; return os; }

   using ::operator<<;
}
9
Serge Rogatch

これは、最初のoperator<<()が名前空間Aの外部で定義されているためです。

1