引数に依存するルックアップとは何かについての良い説明は何ですか?多くの人は、ケーニヒルックアップとも呼んでいます。
できれば知りたい:
ケーニヒルックアップ、または引数依存ルックアップ 、C++のコンパイラが非修飾名を検索する方法を説明します。
C++ 11標準§3.4.2/1の状態:
関数呼び出し(5.2.2)のpostfix-expressionが非修飾IDである場合、通常の非修飾ルックアップ(3.4.1)で考慮されない他の名前空間が検索され、それらの名前空間では、名前空間スコープのフレンド関数宣言( 11.3)他に見えないものが見つかるかもしれません。検索に対するこれらの変更は、引数のタイプ(およびテンプレートテンプレート引数の場合、テンプレート引数の名前空間)に依存します。
簡単に言えば、ニコライ・ジョシュティスは述べています1:
1つ以上の引数タイプが関数のネームスペースで定義されている場合、関数のネームスペースを修飾する必要はありません。
簡単なコード例:
namespace MyNamespace
{
class MyClass {};
void doSomething(MyClass);
}
MyNamespace::MyClass obj; // global object
int main()
{
doSomething(obj); // Works Fine - MyNamespace::doSomething() is called.
}
上記の例では、using
- declarationもusing
- directiveもありませんが、コンパイラは名前空間MyNamespace
で宣言された関数として非修飾名doSomething()
を正しく識別しますKoenigルックアップを適用します。
このアルゴリズムは、ローカルスコープだけでなく、引数の型を含む名前空間も参照するようコンパイラーに指示します。したがって、上記のコードでは、コンパイラは、関数doSomething()
の引数であるオブジェクトobj
がネームスペースMyNamespace
に属していることを検出します。そのため、そのネームスペースを見て、doSomething()
の宣言を見つけます。
上記の簡単なコード例が示すように、Koenigルックアップはプログラマに便利さと使いやすさを提供します。 Koenigルックアップを使用しない場合、完全修飾名を繰り返し指定するか、代わりに多数のusing
-宣言を使用するプログラマーにオーバーヘッドが発生します。
Koenigルックアップに過度に依存すると、セマンティックの問題が発生する可能性があり、プログラマーが不意を突くことがあります。
std::swap
の例を考えてみましょう。これは、2つの値を交換する標準ライブラリアルゴリズムです。 Koenigルックアップでは、このアルゴリズムを使用している間は注意する必要があります:
std::swap(obj1,obj2);
次と同じ動作を示さない場合があります。
using std::swap;
swap(obj1, obj2);
ADLでは、swap
関数のどのバージョンが呼び出されるかは、渡される引数の名前空間に依存します。
名前空間A
が存在し、A::obj1
、A::obj2
&A::swap()
が存在する場合、2番目の例ではA::swap()
が呼び出されます。ユーザーが望んでいたものではありません。
さらに、何らかの理由でA::swap(A::MyClass&, A::MyClass&)
とstd::swap(A::MyClass&, A::MyClass&)
の両方が定義されている場合、最初の例はstd::swap(A::MyClass&, A::MyClass&)
を呼び出しますが、2番目はswap(obj1, obj2)
あいまいです。
元のAT&TおよびBell Labsの研究者およびプログラマーによって考案されたため、Andrew Koenig。
標準C++ 03/11 [basic.lookup.argdep]:3.4.2引数依存の名前ルックアップ。
1Koenigルックアップの定義は、Josuttisの本The C++ Standard Library:A Tutorial and Referenceで定義されています。
Koenig Lookupでは、名前空間を指定せずに関数が呼び出された場合、関数の名前はalso型の名前空間で検索されます引数の定義されています。 引数依存名のルックアップ 、簡単に言えば [〜#〜] adl [〜#〜] としても知られている理由です。
これはケーニッヒ検索のためで、これを書くことができます:
std::cout << "Hello World!" << "\n";
それ以外の場合、次のように記述する必要があります。
std::operator<<(std::operator<<(std::cout, "Hello World!"), "\n");
これは本当にタイピングが多すぎて、コードは本当にいです!
つまり、Koenig Lookupがない場合、Hello Worldプログラムでさえ複雑に見えます。
たぶん、理由から始めて、それから方法に行くのが最善かもしれません。
名前空間が導入されたとき、アイデアはすべてを名前空間で定義し、別々のライブラリが互いに干渉しないようにすることでした。しかし、それは演算子に問題をもたらしました。次のコードの例を見てください。
namespace N
{
class X {};
void f(X);
X& operator++(X&);
}
int main()
{
// define an object of type X
N::X x;
// apply f to it
N::f(x);
// apply operator++ to it
???
}
もちろん、あなたはN::operator++(x)
と書くこともできますが、それは演算子のオーバーロードの全ポイントを打ち負かすでしょう。したがって、コンパイラがスコープ内にないにもかかわらず、operator++(X&)
を見つけることができる解決策を見つける必要がありました。一方、呼び出しをあいまいにする可能性のある別の無関係な名前空間で定義された別のoperator++
をまだ見つけるべきではありません(この単純な例では、あいまいさはありませんが、より複雑な例では、そうするかもしれません)。解決策は引数依存ルックアップ(ADL)で、ルックアップは引数(より正確には引数のタイプ)に依存するため、その方法で呼び出されました。このスキームはAndrew R. Koenigによって考案されたため、Koenigルックアップとも呼ばれます。
トリックは、関数呼び出しの場合、通常の名前検索(使用時にスコープ内の名前を検索する)に加えて、関数に与えられた引数の型のスコープで2回目の検索が行われることです。したがって、上記の例では、x++
をmainで記述すると、グローバルスコープだけでなく、x
、operator++
のタイプが定義されたスコープ、つまりN::X
でもnamespace N
を探します。そして、一致するoperator++
が見つかるため、x++
が機能します。ただし、別の名前空間で定義されている別のoperator++
、たとえばN2
は見つかりません。 ADLは名前空間に制限されないため、f(x)
でN::f(x)
の代わりにmain()
を使用することもできます。
私の意見では、それに関するすべてが良いというわけではありません。コンパイラベンダーを含む人々は、その不幸な振る舞いのためにit辱しています。
ADLは、C++ 11のfor-rangeループの大幅な見直しを担当しています。 ADLが意図しない影響を与えることがある理由を理解するには、引数が定義されている名前空間だけでなく、引数のテンプレート引数の引数、関数型のパラメーター型/それらの引数のポインター型のポインター型の引数も考慮することを考慮してください、など。
ブーストを使用した例
std::vector<boost::shared_ptr<int>> v;
auto x = begin(v);
これは、ユーザーがboost.rangeライブラリーを使用する場合、あいまいさをもたらします。なぜなら、両方のstd::begin
が見つかりました(ADLでstd::vector
)およびboost::begin
が見つかりました(ADLでboost::shared_ptr
)。