「引数依存ルックアップ」(別名ADL、または「ケーニッヒルックアップ」)とは何ですか?


176

引数に依存するルックアップが何であるかについてのいくつかの良い説明は何ですか?多くの人がケーニッヒルックアップとも呼んでいます。

できれば知りたいです。

  • なぜそれが良いのですか?
  • なぜそれが悪いのですか?
  • それはどのように機能しますか?




回答:


223

Koenig LookupまたはArgument Dependent Lookupは、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も-directiveもありませんusingが、コンパイラーはKoenigルックアップを適用することにより、非修飾名doSomething()を名前空間で宣言された関数として正しく識別しますMyNamespace

それはどのように機能しますか?

このアルゴリズムは、ローカルスコープだけでなく、引数の型を含む名前空間も調べるようにコンパイラーに指示します。したがって、上記のコードでは、コンパイラはobj、関数の引数であるオブジェクトdoSomething()が名前空間に属していることを検出しますMyNamespace。そのため、その名前空間を調べて、の宣言を見つけますdoSomething()

Koenigルックアップの利点は何ですか?

上記の単純なコード例が示すように、Koenigルックアップは、プログラマーに便利さと使いやすさを提供します。Koenigルックアップがないと、完全修飾名を繰り返し指定するか、代わりに多数のusing-declarationを使用するために、プログラマーにオーバーヘッドが発生します。

ケーニッヒルックアップの批判はなぜですか?

Koenigルックアップに過度に依存すると、意味上の問題が発生し、プログラマーが油断することがあります。

std::swap2つの値を交換する標準ライブラリアルゴリズムであるの例を考えてみます。Koenigルックアップでは、次の理由により、このアルゴリズムを使用する場合は注意が必要です。

std::swap(obj1,obj2);

次と同じ動作を示さない場合があります。

using std::swap;
swap(obj1, obj2);

ADLでは、どのバージョンのswap関数が呼び出されるかは、渡される引数の名前空間に依存します。

そこに名前空間が存在する場合Aと場合A::obj1A::obj2A::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によって考案されたためです。

参考文献:


1 Koenigルックアップの定義は、Josuttisの著書「C ++標準ライブラリ:チュートリアルとリファレンス」で定義されています。


11
@AlokSave:答えは+1ですが、雑学は正しくありません。KoenigはADLを発明しませんでした、彼はここで告白します :)
legends2k 14

20
ケーニッヒアルゴリズムの批判の例は、ケーニッヒルックアップの「機能」と同様に「機能」と見なすことができます。このような方法でstd :: swap()を使用することは一般的なイディオムです。より特殊なバージョンA :: swap()が提供されない場合に備えて、「std :: swap()を使用する」を提供します。A :: swap()の特別なバージョンが利用可能な場合、通常そのバージョンを呼び出す必要があります。これにより、コンパイルと作業の呼び出しを信頼できるため、swap()呼び出しの汎用性が高まりますが、より特化したバージョンがある場合は、それを使用して信頼することもできます。
Anthony Hall

6
@anthrondこれにはもっとあります。std::swap唯一の選択肢を追加することですので、あなたが実際にそれをしなければならないstd::swapあなたのためのテンプレート関数の明示的な特殊化をAクラス。ただし、Aクラス自体がテンプレートの場合、明示的な特殊化ではなく部分的な特殊化になります。また、テンプレート関数の部分的な特殊化は許可されていません。のオーバーロードを追加std::swapすることもできますが、明示的に禁止されています(std名前空間に追加することはできません)。したがって、ADLがの唯一の方法ですstd::swap
Adam Badura

1
「koenigルックアップの利点」で、オーバーロードされた演算子について言及することを期待していました。の例std::swap()は少し逆のようです。std::swap()タイプ固有のオーバーロードではなく、が選択されている場合に問題が発生すると思いますA::swap()。の例std::swap(A::MyClass&, A::MyClass&)は誤解を招くようです。以来、stdユーザーのタイプに固有の過負荷を持っていることはない、私はそれは素晴らしい例だとは思いません。
2016年

1
@gsamaras ...そして?関数が定義されていないことがよくわかります。あなたのエラーメッセージはMyNamespace::doSomething、それだけではなくを探しているため、実際に機能したことを証明してい::doSomethingます。
モニカの訴訟に資金を提供

69

Koenig Lookupでは、名前空間を指定せずに関数が呼び出されると、引数の型が定義されている名前空間で関数の名前検索されます。これが、Argument-Dependent name Lookup、つまりADLとも呼ばれる理由です。

これはケーニッヒルックアップのため、次のように書くことができます。

std::cout << "Hello World!" << "\n";

それ以外の場合は、次のように記述する必要があります。

std::operator<<(std::operator<<(std::cout, "Hello World!"), "\n");

これは本当にタイピングが多すぎて、コードは本当に醜く見えます!

つまり、ケーニッヒルックアップがない場合、Hello Worldプログラムでさえ複雑に見えます。


12
説得力のある例。
Anthony Hall

10
@AdamBadura:std::coutこれは関数への1つの引数であり、ADLを有効にするのに十分であることに注意してください。気づきましたか?
Nawaz

1
@meet:あなたの質問には、このスペースでは提供できない長い回答が必要です。したがって、私は次のようなトピックについてのみ読むことをお勧めしますostream<<。2)完全修飾名(std::vectorまたはなどstd::operator<<)。3)引数依存ルックアップのより詳細な研究。
Nawaz、2015年

2
@WorldSEnder:はい、そうです。std::endl引数として使用できる関数は、実際にはメンバー関数です。とにかく、の"\n"代わりに使用した場合std::endl、私の答えは正しいです。コメントをありがとう。
Nawaz

2
@Destructor:の形式の関数f(a,b)呼び出しはフリー関数を呼び出します。したがって、の場合、2番目の引数std::operator<<(std::cout, std::endl);をとるそのようなフリー関数はありませんstd::endl。これは、std::endl引数として取るメンバー関数であり、そのために記述する必要がありますstd::cout.operator<<(std::endl);。そして、2番目の引数として使用するフリー 関数があるため、機能します。同様に動作します。char const*"\n"'\n'
Nawaz

30

多分それは理由から始めて、それから方法に行くのが最善です。

名前空間が導入されたときのアイデアは、すべてを名前空間で定義して、個別のライブラリが互いに干渉しないようにすることでした。しかし、それはオペレーターに問題をもたらしました。たとえば、次のコードを見てください。

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++呼び出しをあいまいにする可能性のある別の無関係な名前空間で定義された別の名前はまだ検出されません(この単純な例では、あいまいさはありませんが、より複雑な例では可能性があります)。解決策はArgument Dependent Lookup(ADL)でした。これは、ルックアップが引数(正確には、引数の型)に依存するため、そのように呼ばれていました。このスキームはAndrew R. Koenigによって発明されたため、Koenig lookupとも呼ばれます。

トリックは、関数呼び出しの場合、通常の名前ルックアップ(使用時にスコープ内の名前を見つける)に加えて、関数に指定された引数の型のスコープで2回目のルックアップが行われることです。あなたが書くのであれば、上記の例では、x++メインで、それが探しoperator++グローバルスコープでだけではなく、さらに種類の範囲でxN::Xすなわちで、定義されましたnamespace N。そして、そこに一致するが見つかるoperator++のでx++、うまくいきます。もう一つoperator++別の名前空間で定義されているが、たとえばN2、しかし、検出されません。ADLは名前空間に限定されないためf(x)N::f(x)inの代わりに使用することもできますmain()


ありがとう!なぜそこにあったのか本当に理解したことはありません!
user965369

20

私の意見では、それについてすべてが良いとは限りません。コンパイラベンダを含む人々は、その不幸な振る舞いのために侮辱しています。

ADLは、C ++ 11のfor-rangeループの大幅な見直しを担当しています。ADLが意図しない影響を与えることがある理由を理解するには、引数が定義されている名前空間だけでなく、引数のテンプレート引数、関数型のパラメーター型、それらの引数のポインター型のポインター型も考慮されることを考慮してくださいなど。

ブーストを使用した例

std::vector<boost::shared_ptr<int>> v;
auto x = begin(v);

これは、ユーザーがboost.rangeライブラリーを使用する場合、あいまいさをもたらしました。なぜなら、両方std::beginが(ADLを使用してstd::vectorboost::begin検出され、(ADLを使用してboost::shared_ptr)検出されるためです。


そもそも、テンプレートの議論を検討することにはどのようなメリットがあるのか​​、いつも疑問に思っていました。
Dennis Zickefoose 2011年

ADLはオペレーターにのみ推奨され、他の関数の名前空間を明示的に記述する方が良いと言っても過言ではありませんか?
balki 2013

引数の基本クラスの名前空間も考慮されますか?(もちろん、それは狂っていると思います)。
Alex B

3
直し方?std :: begin?
ポール、2014

2
@paulmはい、std::begin名前空間のあいまいさを解消します。
ニコス
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.