実引数依存の名前探索
引数依存の名前探索 (ADL: Argument-Dependent Lookup) は、Koenig lookup[1] とも呼ばれ、関数呼び出し式において非修飾関数名を探索するための一連のルールであり、オーバーロードされた演算子への暗黙的な関数呼び出しも含まれます。これらの関数名は、通常の非修飾名探索で考慮されるスコープと名前空間に加えて、引数の名前空間でも探索されます。
引数依存の名前探索により、異なる名前空間で定義された演算子を使用することが可能になります。例:
#include <iostream> int main() { std::cout << "Test\n"; // There is no operator<< in global namespace, but ADL // examines std namespace because the left argument is in // std and finds std::operator<<(std::ostream&, const char*) operator<<(std::cout, "Test\n"); // Same, using function call notation // However, std::cout << endl; // Error: “endl” is not declared in this namespace. // This is not a function call to endl(), so ADL does not apply endl(std::cout); // OK: this is a function call: ADL examines std namespace // because the argument of endl is in std, and finds std::endl (endl)(std::cout); // Error: “endl” is not declared in this namespace. // The sub-expression (endl) is not an unqualified-id }
目次 |
[編集] 詳細
まず、通常の非修飾名前探索によって生成された探索セットに次のいずれかが含まれている場合、引数依存の名前探索は考慮されません。
それ以外の場合、関数呼び出し式の各引数について、その型が調べられ、探索に追加する関連する名前空間とクラスのセットが決定されます。
T へのポインタ、または型 T の配列へのポインタの引数については、型 T が調べられ、その関連するクラスと名前空間のセットがセットに追加されます。X のメンバー関数 F へのポインタの引数については、関数パラメータ型、関数戻り値の型、およびクラス X が調べられ、その関連するクラスと名前空間のセットがセットに追加されます。X のデータメンバー T へのポインタの引数については、メンバー型と型 X の両方が調べられ、その関連するクラスと名前空間のセットがセットに追加されます。- さらに、オーバーロードのセットがテンプレート識別子によって名前付けされている場合、そのすべての型テンプレート引数とテンプレートテンプレート引数(ただし非型テンプレート引数は含まない)が調べられ、その関連するクラスと名前空間のセットがセットに追加されます。
|
関連するクラスと名前空間のセット内のいずれかの名前空間がインライン名前空間である場合、その囲む名前空間もセットに追加されます。 関連するクラスと名前空間のセット内のいずれかの名前空間が直接インライン名前空間を含んでいる場合、そのインライン名前空間がセットに追加されます。 |
(C++11以降) |
関連するクラスと名前空間のセットが決定された後、このセットのクラスで見つかったすべての宣言は、以下の点2で述べられている名前空間スコープのフレンド関数および関数テンプレートを除き、ADLのさらなる処理のために破棄されます。
通常の非修飾名前探索によって見つかった宣言のセットと、ADLによって生成された関連するセットのすべての要素で見つかった宣言のセットは、以下の特別なルールに従ってマージされます。
[編集] 備考
引数依存の名前探索のため、クラスと同じ名前空間で定義された非メンバー関数および非メンバー演算子は、そのクラスの公開インターフェースの一部と見なされます(ADLを通じて見つかる場合)[2]。
ADLは、ジェネリックコードで2つのオブジェクトを交換するための確立されたイディオムの理由です。using std::swap; swap(obj1, obj2); なぜなら、std::swap(obj1, obj2) を直接呼び出すと、obj1 または obj2 の型と同じ名前空間で定義されている可能性のあるユーザー定義の swap() 関数は考慮されず、非修飾の swap(obj1, obj2) を呼び出すだけでは、ユーザー定義のオーバーロードが提供されていない場合は何も呼び出されないからです。特に、std::iter_swap および他のすべての標準ライブラリアルゴリズムは、Swappable 型を扱う際にこのアプローチを使用します。
名前探索ルールにより、std 名前空間の型を操作する演算子をグローバル名前空間またはユーザー定義名前空間で宣言することは非現実的です。例えば、std::vector や std::pair のためのカスタムの operator>> や operator+ など(ただし、vector/pair の要素型がユーザー定義型であり、その名前空間がADLに追加される場合は除く)。そのような演算子は、標準ライブラリのアルゴリズムなどのテンプレートインスタンス化からは探索されません。詳細については、依存名を参照してください。
ADLは、クラスまたはクラステンプレート内に完全に定義されているフレンド関数(通常はオーバーロードされた演算子)を、それが名前空間レベルで一度も宣言されていなくても見つけることができます。
template<typename T> struct number { number(int); friend number gcd(number x, number y) { return 0; }; // Definition within // a class template }; // Unless a matching declaration is provided gcd is // an invisible (except through ADL) member of this namespace void g() { number<double> a(3), b(4); a = gcd(a, b); // Finds gcd because number<double> is an associated class, // making gcd visible in its namespace (global scope) // b = gcd(3, 4); // Error; gcd is not visible }
|
通常の探索で何も見つからなくてもADLを通じて関数呼び出しが解決されることがありますが、明示的に指定されたテンプレート引数を持つ関数テンプレートへの関数呼び出しは、通常の探索で見つかるテンプレートの宣言が必要です(そうでない場合、不明な名前の後に小なり文字が続くのは構文エラーです)。 namespace N1 { struct S {}; template<int X> void f(S); } namespace N2 { template<class T> void f(T t); } void g(N1::S s) { f<3>(s); // Syntax error until C++20 (unqualified lookup finds no f) N1::f<3>(s); // OK, qualified lookup finds the template 'f' N2::f<3>(s); // Error: N2::f does not take a non-type parameter // N1::f is not looked up because ADL only works // with unqualified names using N2::f; f<3>(s); // OK: Unqualified lookup now finds N2::f // then ADL kicks in because this name is unqualified // and finds N1::f } |
(C++20まで) |
以下のコンテキストでは、ADLのみの探索(つまり、関連する名前空間のみでの探索)が行われます。
|
(C++11以降) |
- テンプレートインスタンス化の時点からの依存名探索。
|
(C++17以降) |
[編集] 例
| このセクションは未完成です 理由:さらなる例 |
http://www.gotw.ca/gotw/030.htm からの例
namespace A { struct X; struct Y; void f(int); void g(X); } namespace B { void f(int i) { f(i); // Calls B::f (endless recursion) } void g(A::X x) { g(x); // Error: ambiguous between B::g (ordinary lookup) // and A::g (argument-dependent lookup) } void h(A::Y y) { h(y); // Calls B::h (endless recursion): ADL examines the A namespace // but finds no A::h, so only B::h from ordinary lookup is used } }
[編集] 欠陥報告
以下の動作変更を伴う欠陥報告が、以前に公開されたC++標準に遡って適用されました。
| DR | 適用対象 | 公開された動作 | 正しい動作 |
|---|---|---|---|
| CWG 33 | C++98 | 関連する名前空間またはクラスが不特定 探索に使用される引数が オーバーロードされた関数グループまたは関数テンプレートのアドレスである場合 |
指定された |
| CWG 90 | C++98 | ネストされた非共用体クラスの関連するクラスに その囲むクラスが含まれていなかったが、ネストされた 共用体はその囲むクラスに関連付けられていた |
非共用体も関連付けられる |
| CWG 239 | C++98 | 通常の非修飾探索で見つかったブロックスコープの関数宣言が ADLの発生を妨げなかった |
ADLは考慮されない、ただし using 宣言の場合を除く |
| CWG 997 | C++98 | 依存するパラメータ型と戻り値の型が 関数テンプレートの関連するクラスと名前空間を決定する際に 考慮から除外されていた |
含まれる |
| CWG 1690 | C++98 C++11 |
ADLは、返されるラムダ(C++11)または ローカルクラス型のオブジェクト(C++98)を見つけることができなかった |
見つけることができる |
| CWG 1691 | C++11 | ADLは不透明な列挙型宣言に対して予期しない動作を示した | fixed |
| CWG 1692 | C++98 | 二重にネストされたクラスには関連する名前空間がなかった (それらの囲むクラスはどの名前空間のメンバーでもない) |
関連する名前空間は 最も内側の 囲む名前空間に拡張される |
| CWG 2857 | C++98 | 不完全なクラス型の関連するクラスに その基底クラスが含まれていた |
含まれない |
[編集] 関連項目
[編集] 外部リンク
|