実引数依存の名前探索
実引数依存の名前探索 (ADL または Koenig lookup とも言います) は、関数呼び出し式 (オーバーロードされた演算子に対する暗黙の関数呼び出しを含みます) における名前探索のためのルールの集合です。 これらの関数名は、通常の非修飾名の名前探索によって考慮されるスコープおよび名前空間に加えて、その実引数の名前空間でも名前探索されます。
実引数依存の名前探索により、異なる名前空間で定義された演算子が使用可能となります。 例:
#include <iostream>int main(){std::cout<<"Test\n";// operator<< はグローバル名前空間にはありませんが、// 左の引数が std 名前空間であるため、 ADL によって std が調べられ、// std::operator<<(std::ostream&, const char*) が発見されます。 operator<<(std::cout, "Test\n");// 同上。 関数呼び出し表記を使用。 // しかし、std::cout<< endl;// エラー。 「endl」はこの名前空間では宣言されていません。// これは endl の関数呼び出しではないため、 ADL は適用されません。 endl(std::cout);// OK。 これは関数呼び出しです。 endl の引数が std 名前空間であるため、// ADL によって std 名前空間が調べられ、 std::endl が発見されます。 (endl)(std::cout);// エラー。 「endl」はこの名前空間では宣言されていません。// 部分式 (endl) は関数呼び出し式ではありません。}
目次 |
[編集]詳細
まず、通常の非修飾名の名前探索によって生成される名前探索集合が以下のいずれかを含む場合、実引数依存の名前探索は考慮されません。
そうでなければ、名前探索に追加する関連する名前空間とクラスの集合を決定するために、関数呼び出し式内のすべての実引数について、その型が調べられます。
関連するクラスと名前空間の集合に含まれる名前空間がインライン名前空間の場合は、その囲っている名前空間も集合に追加されます。
関連するクラスと名前空間の集合に含まれる名前空間がインライン名前空間を直接含む場合は、そのインライン名前空間が集合に追加されます。
関連するクラスと名前空間の集合が決定された後、その集合のクラス内に発見されるすべての宣言が、下の第2項で述べるように名前空間スコープのフレンド関数および関数テンプレートを除いて、 ADL の目的に対しては破棄されます。
通常の非修飾名の名前探索によって発見された宣言の集合と、 ADL によって生成された関連する集合のすべての要素内に発見された宣言の集合が、以下の特別なルールを用いて、マージされます。
[編集]ノート
実引数依存の名前探索のために、クラスと同じ名前空間に定義されている非メンバ関数および非メンバ演算子はそのクラスのパブリックインタフェースの一部であるとみなされます (それらが ADL を通して発見される場合)[1]。
ADL は、総称コードで2つのオブジェクトをスワップする確立されたイディオムusingstd::swap; swap(obj1, obj2);
名前探索のルールは、 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){return0;};// クラステンプレート内の// 定義};// 対応する宣言が提供されない限り、 gcd は不可視です (ADL を通した場合を除いて)。// この名前空間のメンバvoid g(){ number<double> a(3), b(4); a = gcd(a,b);// number<double> が関連するクラスであり、 gcd をその名前空間// (グローバルスコープ) 内で可視にするため、 gcd を発見します。// b = gcd(3,4); // エラー、 gcd は可視ではありません。}
関数呼び出しは、たとえ通常の名前探索が何も発見しない場合でも 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);// C++20 未満は構文エラー (非修飾名の名前探索は f を発見しません)。 N1::f<3>(s);// OK、修飾名の名前探索はテンプレート「f」を発見します。 N2::f<3>(s);// エラー、 N2::f は非型引数を取りません。// ADL は非修飾名を用いたときしか動作しないため、// N1::f は名前探索されません。using N2::f; f<3>(s);// OK、非修飾名の名前探索は今回は N2::f を発見します。// その後、この名前が非修飾であるため ADL が作動し、// N1::f を発見します。} | (C++20未満) |
以下の文脈では、 ADL のみの名前探索 (つまり、関連する名前空間内のみでの名前探索) が行われます。
| (C++17以上) |
[編集]例
This section is incomplete Reason: more examples |
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 のみが使用されます。}}
[編集]関連項目
[編集]参考文献
- ↑ H. Sutter (1998) "What's In a Class? - The Interface Principle" in C++ Report, 10(3)