引数に依存するルックアップが何であるかについてのいくつかの良い説明は何ですか?多くの人がケーニッヒルックアップとも呼んでいます。
できれば知りたいです。
- なぜそれが良いのですか?
- なぜそれが悪いのですか?
- それはどのように機能しますか?
std::cout << "Hello world";
コンパイルされないでしょう
引数に依存するルックアップが何であるかについてのいくつかの良い説明は何ですか?多くの人がケーニッヒルックアップとも呼んでいます。
できれば知りたいです。
std::cout << "Hello world";
コンパイルされないでしょう
回答:
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ルックアップがないと、完全修飾名を繰り返し指定するか、代わりに多数のusing
-declarationを使用するために、プログラマーにオーバーヘッドが発生します。
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引数に依存する名前の検索。
1 Koenigルックアップの定義は、Josuttisの著書「C ++標準ライブラリ:チュートリアルとリファレンス」で定義されています。
std::swap
唯一の選択肢を追加することですので、あなたが実際にそれをしなければならないstd::swap
あなたのためのテンプレート関数の明示的な特殊化をA
クラス。ただし、A
クラス自体がテンプレートの場合、明示的な特殊化ではなく部分的な特殊化になります。また、テンプレート関数の部分的な特殊化は許可されていません。のオーバーロードを追加std::swap
することもできますが、明示的に禁止されています(std
名前空間に追加することはできません)。したがって、ADLがの唯一の方法ですstd::swap
。
std::swap()
は少し逆のようです。std::swap()
タイプ固有のオーバーロードではなく、が選択されている場合に問題が発生すると思いますA::swap()
。の例std::swap(A::MyClass&, A::MyClass&)
は誤解を招くようです。以来、std
ユーザーのタイプに固有の過負荷を持っていることはない、私はそれは素晴らしい例だとは思いません。
MyNamespace::doSomething
、それだけではなくを探しているため、実際に機能したことを証明してい::doSomething
ます。
Koenig Lookupでは、名前空間を指定せずに関数が呼び出されると、引数の型が定義されている名前空間で関数の名前も検索されます。これが、Argument-Dependent name Lookup、つまりADLとも呼ばれる理由です。
これはケーニッヒルックアップのため、次のように書くことができます。
std::cout << "Hello World!" << "\n";
それ以外の場合は、次のように記述する必要があります。
std::operator<<(std::operator<<(std::cout, "Hello World!"), "\n");
これは本当にタイピングが多すぎて、コードは本当に醜く見えます!
つまり、ケーニッヒルックアップがない場合、Hello Worldプログラムでさえ複雑に見えます。
std::cout
これは関数への1つの引数であり、ADLを有効にするのに十分であることに注意してください。気づきましたか?
ostream<<
。2)完全修飾名(std::vector
またはなどstd::operator<<
)。3)引数依存ルックアップのより詳細な研究。
std::endl
引数として使用できる関数は、実際にはメンバー関数です。とにかく、の"\n"
代わりに使用した場合std::endl
、私の答えは正しいです。コメントをありがとう。
f(a,b)
呼び出しはフリー関数を呼び出します。したがって、の場合、2番目の引数std::operator<<(std::cout, std::endl);
をとるそのようなフリー関数はありませんstd::endl
。これは、std::endl
引数として取るメンバー関数であり、そのために記述する必要がありますstd::cout.operator<<(std::endl);
。そして、2番目の引数として使用するフリー 関数があるため、機能します。同様に動作します。char const*
"\n"
'\n'
多分それは理由から始めて、それから方法に行くのが最善です。
名前空間が導入されたときのアイデアは、すべてを名前空間で定義して、個別のライブラリが互いに干渉しないようにすることでした。しかし、それはオペレーターに問題をもたらしました。たとえば、次のコードを見てください。
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++
グローバルスコープでだけではなく、さらに種類の範囲でx
、N::X
すなわちで、定義されましたnamespace N
。そして、そこに一致するが見つかるoperator++
のでx++
、うまくいきます。もう一つoperator++
別の名前空間で定義されているが、たとえばN2
、しかし、検出されません。ADLは名前空間に限定されないためf(x)
、N::f(x)
inの代わりに使用することもできますmain()
。
私の意見では、それについてすべてが良いとは限りません。コンパイラベンダを含む人々は、その不幸な振る舞いのために侮辱しています。
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
)検出されるためです。
std::begin
名前空間のあいまいさを解消します。