コードがあなたが投稿したものと本当に似ている場合、それはコンパイラの問題です。コードは、必要に応じてclang++で正常にコンパイルされます。
フレンド宣言は、名前空間スコープを持つ関数を宣言するという点で奇妙ですが、宣言はADLを介してのみ使用でき、それでも、引数の少なくとも1つがフレンド宣言を持つクラスのタイプである場合にのみ使用できます。名前空間レベルの宣言がない限り、関数は名前空間スコープで使用できません。
テスト1
(明示的な宣言がない場合、名前空間レベルでは関数を使用できません):
namespace A {
struct B {
friend void f(); // [1]
};
// void f(); // [2]
}
void A::f() {} // [3]
void A::f()
[1]に、のフレンドとして宣言するフレンド宣言を追加しA::B
ます。名前空間レベルで[2]に追加の宣言がないと、[3]の定義はコンパイルに失敗します。これは、A
名前空間の外にあるため、その定義も自己宣言ではないためです。
Matrix<T>
ここでの意味は、関数は名前空間レベルでのルックアップには使用できないため、 (特定のインスタンス化タイプの) ADLを介してのみ使用できるためT
、コンパイラーは2つの値のスワップとの一致としてそれを検出できない可能性がありint
ます。
彼の答えの中で、Jesse Goodは、Matrixテンプレート内でフレンド関数を定義したので、MatrixとMatrixの各インスタンス化には、スワップ関数の同じ定義が含まれると述べています。
クラス内で定義されたフレンド関数は、名前空間レベルの関数を宣言および定義します。この場合も、宣言はクラス内でのみ使用可能であり、ADLを介してアクセスできます。これがテンプレート内で行われると、関数を使用するテンプレートのインスタンス化ごとに、名前空間レベルでテンプレート化されていない無料の関数が定義されます。つまり、異なる定義が生成されます。クラステンプレートスコープ内では、テンプレートの名前はインスタンス化されている特殊化を識別します。つまり、内部Matrix<T>
では、識別子Matrix
はテンプレートの名前ではなく、テンプレートの1つのインスタンス化を示します。
テスト2
namespace X {
template <typename T>
struct A {
friend void function( A ) {}
};
template <typename T>
void funcTion( A<T> ) {}
}
int main() {
using namespace X;
A<int> ai; function(ai); funcTion(ai);
A<double> ad; function(ad); funcTion(ad);
}
$ make test.cpp
$ nm test | grep func | c++filt
0000000100000e90 T void X::funcTion<double>(A<double>)
0000000100000e80 T void X::funcTion<int>(A<int>)
0000000100000e70 T X::function(A<double>)
0000000100000e60 T X::function(A<int>)
の出力はnm
シンボルのリストでありc++filt
、マングルされた名前をC++構文の同等のものに変換します。プログラムの出力は、 2つのタイプに対してインスタンス化されたテンプレートであり、X::funcTion
2X::function
つのオーバーロードされた非テンプレート関数であることを明確に示しています。繰り返しますが、 2つのテンプレート化されていない関数。
同じ関数を生成すると主張することはほとんど意味がありません。たとえばstd::cout << lhs
、コードがoperator<<
関数の現在のインスタンス化のためにの正しいオーバーロードを選択する必要があるなど、関数呼び出しがあったことを考慮してください。aとoroperator<<
をとることができるシングルはありません(どのタイプでもテンプレートをインスタンス化することを妨げるものは何もありません。int
unsigned long
std::vector<double>
ildjarnによる回答は代替案を提案していますが、動作の説明は提供していません。usingディレクティブはusing宣言とは完全に異なるため、代替手段は機能します。特に、前者(using namespace X;
)はルックアップを変更して、名前空間Xの識別子が現在のコードの囲んでいる名前空間の1つで名前空間レベルで使用できるようにします(名前空間のツリーを構築する場合、宣言はブランチの場所で使用できます含むは、 using-directiveX
が使用されるコードを含むブランチと一致します)。
一方、using-declaration(using std::swap;
)は、 using-declarationが存在するコンテキストで関数の宣言を提供します。これが、スワップ関数を実装するためにusing-declarationsを使用し、 using-directivesを使用しない必要がある理由です。std::swap
テスト3
namespace Y { struct Z {}; void swap( Z&,Z& ); }
namespace X {
struct A { int a; Y::Z b; };
void swap( A& lhs, A& rhs ) {
//using namespace std; // [1]
using std::swap; // [2]
swap( lhs.a, rhs.a ); // [3]
swap( lhs.b, rhs.b ); // [4]
}
}
using-directive [1]を使用した場合、::std
名前空間のシンボルは、::X::swap(X::A&,X::A&)
で宣言されているかのように関数内で実行されるルックアップに使用できます(これはと::
の共通の祖先です)。これで、[3]のスワップは、ADLを介して関数を検出しないため、囲んでいる名前空間で関数の検索を開始します。最初に囲む名前空間はであり、関数が含まれているため、ルックアップが停止し、オーバーロードの解決が開始されますが、の有効なオーバーロードではないため、コンパイルに失敗します。::std
::X
swap
swap
X
swap
X::swap
swap(int&,int&)
using-declarationを使用することにより、宣言を(関数内で!)std::swap
のスコープに持ち込みます。X::swap
この場合も、[3]ではADLは適用されず、ルックアップが開始されます。現在のスコープ(関数内)では、の宣言が検出std::swap
され、テンプレートがインスタンス化されます。[4]では、ADLが起動し、関数内および/またはでswap
定義された関数を検索し、それを現在のスコープで見つかったオーバーロードのセットに追加します(ここでも、)。この時点で、はよりも適切に一致し(完全に一致する場合、テンプレート化されていない関数はテンプレート化された関数よりも適切に一致します)、期待される動作が得られます。::Y::Z
::Y
::std::swap
::Z::swap
std::swap