Argument-dependent lookup
実引数依存の名前探索 (ADL)、別名 Koenig lookup [1] 、は 関数呼び出し式 における非修飾関数名を探索するための規則群であり、 オーバーロードされた演算子 への暗黙的な関数呼び出しも含みます。これらの関数名は、通常の 非修飾名前探索 で考慮されるスコープと名前空間に加えて、実引数の名前空間でも探索されます。
実引数依存の名前探索(ADL)により、異なる名前空間で定義された演算子を使用することが可能になります。例:
#include <iostream> int main() { std::cout << "Test\n"; // グローバル名前空間にはoperator<<は存在しないが、ADLにより // 左側の実引数がstd名前空間にあるためstd名前空間を調査し、 // std::operator<<(std::ostream&, const char*)を見つける operator<<(std::cout, "Test\n"); // 同じ処理を関数呼び出し記法で記述 // しかしながら、 std::cout << endl; // エラー: 「endl」はこの名前空間で宣言されていない // これはendl()への関数呼び出しではないため、ADLは適用されない endl(std::cout); // OK: これは関数呼び出しであるため、ADLがstd名前空間を調査 // endlの実引数がstd名前空間にあるため、std::endlを見つける (endl)(std::cout); // エラー: 「endl」はこの名前空間で宣言されていない // 部分式(endl)は非修飾名ではない }
目次 |
詳細
まず、通常の 非修飾名探索 によって生成された探索セットが以下のいずれかを含む場合、引数依存探索は考慮されません:
それ以外の場合、関数呼び出し式の各引数について、その型が調べられ、ルックアップに追加される 関連する名前空間とクラスの集合 が決定されます。
T
へのポインタ、または
T
の配列へのポインタ型の引数に対しては、型
T
が調べられ、それに関連付けられたクラスと名前空間の集合が集合に追加されます。
F
の引数について、
X
クラスのメンバ関数ポインタ型
F
の関数パラメータ型、関数戻り値型、およびクラス
X
が検査され、それらに関連するクラスと名前空間の集合が集合に追加されます。
T
クラス
X
のメンバ型と型
X
の両方が調べられ、それらに関連するクラスと名前空間の集合が集合に追加されます。
- さらに、オーバーロード集合が テンプレート識別子 によって指定されている場合、そのすべての型テンプレート引数とテンプレートテンプレート引数(ただし定数テンプレート引数は除く)が検査され、関連するクラスと名前空間の集合がセットに追加されます。
|
関連するクラスと名前空間の集合内のいずれかの名前空間が inline namespace である場合、その外側の名前空間も集合に追加されます。 関連するクラスと名前空間の集合内のいずれかの名前空間が直接inline namespaceを含む場合、そのinline namespaceは集合に追加されます。 |
(C++11以降) |
関連するクラスと名前空間の集合が決定された後、この集合のクラス内で見つかったすべての宣言は、以下のポイント2で述べられているように、名前空間スコープのフレンド関数と関数テンプレートを除き、それ以降のADL処理の目的では破棄されます。
通常の unqualified lookup によって見つかった宣言の集合と、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
名前空間の型を操作する演算子(例えば、
operator
>>
や
operator
+
を
std::vector
や
std::pair
に対して宣言すること)は非現実的です(ただし、vector/pairの要素型がユーザー定義型である場合は除きます。その場合、ADLにそれらの名前空間が追加されます)。このような演算子は、標準ライブラリのアルゴリズムなどのテンプレートインスタンス化からは探索されません。詳細は
dependent names
を参照してください。
ADLは、 friend function (通常はオーバーロードされた演算子)を、名前空間レベルで宣言されていなかった場合でも、クラスまたはクラステンプレート内で完全に定義されているものを見つけることができます。
template<typename T> struct number { number(int); friend number gcd(number x, number y) { return 0; }; // クラステンプレート内での定義 // }; // 一致する宣言が提供されない限り、gcdはこの名前空間の // (ADLを除いて)不可視のメンバーとなる void g() { number<double> a(3), b(4); a = gcd(a, b); // number<double>が関連クラスであるためgcdを発見し、 // その名前空間(グローバルスコープ)で可視となる // b = gcd(3, 4); // エラー; gcdは可視ではない }
|
通常のルックアップで何も見つからなくてもADLを通じて関数呼び出しが解決される場合があるが、明示的にテンプレート引数を指定した function template への関数呼び出しでは、通常のルックアップによってテンプレートの宣言が見つかる必要がある(そうでない場合、未知の名前に続いて小なり記号が現れるのは構文エラーとなる)。 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 constant 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); // B::fを呼び出す(無限再帰) } void g(A::X x) { g(x); // エラー: B::g(通常の名前探索)とA::g(実引数依存探索)の間で曖昧 } void h(A::Y y) { h(y); // B::hを呼び出す(無限再帰): ADLはA名前空間を調査するが // A::hが見つからないため、通常の名前探索からのB::hのみが使用される } }
不具合報告
以下の動作変更の欠陥報告書は、以前に公開されたC++規格に対して遡及的に適用されました。
| DR | 適用対象 | 公開時の動作 | 正しい動作 |
|---|---|---|---|
| CWG 33 | C++98 |
ルックアップに使用される引数がオーバーロードされた関数群のアドレスまたは関数テンプレートの場合、
関連する名前空間またはクラスが未規定 |
規定 |
| CWG 90 | C++98 |
ネストされた非共用体クラスの関連クラスにその外側のクラスが含まれていなかったが、
ネストされた共用体はその外側のクラスと関連していた |
非共用体も関連付けられる |
| CWG 239 | C++98 |
通常の非修飾名探索で見つかったブロックスコープの関数宣言が
ADLの発生を妨げなかった |
using
宣言を除いて
ADLは考慮されない |
| CWG 997 | C++98 |
関数テンプレートの関連クラスと名前空間を決定する際に、
依存するパラメータ型と戻り値の型が考慮から除外されていた |
含まれる |
| CWG 1690 |
C++98
C++11 |
ADLは返されるラムダ(C++11)またはローカルクラス型の
オブジェクト(C++98)を見つけることができなかった |
見つけることができる |
| CWG 1691 | C++11 | ADLが不透明な列挙型宣言に対して驚くべき動作を示した | 修正 |
| CWG 1692 | C++98 |
二重にネストされたクラスには関連する名前空間がなかった
(それらの外側のクラスはどの名前空間のメンバーでもない) |
関連する名前空間は
最も内側の外側の 名前空間まで拡張される |
| CWG 2857 | C++98 |
不完全なクラス型の関連クラスに
その基底クラスが含まれていた |
含まれない |
関連項目
外部リンク
|