少し前に、引数依存ルックアップのいくつかの落とし穴を説明した記事を読みましたが、もう見つかりません。アクセスしてはいけないものにアクセスすること、またはそのようなものにアクセスすることについてでした。そこで、ここで質問したいと思います: ADL の落とし穴は何ですか?
2 に答える
引数依存のルックアップには大きな問題があります。たとえば、次のユーティリティを考えてみましょう。
#include <iostream>
namespace utility
{
template <typename T>
void print(T x)
{
std::cout << x << std::endl;
}
template <typename T>
void print_n(T x, unsigned n)
{
for (unsigned i = 0; i < n; ++i)
print(x);
}
}
簡単ですよね?任意のオブジェクトを呼び出しprint_n()
て渡すことができprint
、オブジェクト時間を印刷するために呼び出しますn
。
実際、このコードを見ただけでは、 によってどの関数が呼び出されるのかまったくわかりませんprint_n
。print
ここで指定された関数テンプレートである可能性がありますが、そうではない可能性があります。なんで?引数依存のルックアップ。
例として、ユニコーンを表すクラスを作成したとします。何らかの理由でprint
、逆参照された null ポインターに書き込むことによってプログラムをクラッシュさせるだけの名前の関数も定義しました (偶然です!) (なぜこれを行ったのかは誰にもわかりませんが、それは重要ではありません)。
namespace my_stuff
{
struct unicorn { /* unicorn stuff goes here */ };
std::ostream& operator<<(std::ostream& os, unicorn x) { return os; }
// Don't ever call this! It just crashes! I don't know why I wrote it!
void print(unicorn) { *(int*)0 = 42; }
}
次に、ユニコーンを作成して 4 回印刷する小さなプログラムを作成します。
int main()
{
my_stuff::unicorn x;
utility::print_n(x, 4);
}
このプログラムをコンパイルして実行すると... クラッシュします。「何?! まさか」とあなたは言います:「私print_n
は を呼び出しました。これはprint
、ユニコーンを印刷する関数を 4 回呼び出します!」はい、それは本当ですが、print
あなたが期待していた関数を呼び出していません。と呼ばれていmy_stuff::print
ます。
選ばれる理由はmy_stuff::print
?名前の検索中に、コンパイラは、への呼び出しの引数が、名前空間で宣言されているクラス型である型であることを確認しprint
ます。 unicorn
my_stuff
引数依存のルックアップのため、コンパイラは という名前の候補関数の検索にこの名前空間を含めますprint
。が見つかりmy_stuff::print
、オーバーロードの解決時に実行可能な最適な候補として選択されます。候補print
関数のいずれかを呼び出すために変換は必要なく、関数テンプレートよりも非テンプレート関数が優先されるため、非テンプレート関数my_stuff::print
が最適です。
(これが信じられない場合は、この質問のコードをそのままコンパイルして、ADL の動作を確認してください。)
はい、引数依存のルックアップは C++ の重要な機能です。オーバーロードされた演算子などの一部の言語機能の目的の動作を実現するには、基本的に必要です (ストリーム ライブラリを検討してください)。とはいえ、それには非常に欠陥があり、本当に醜い問題につながる可能性があります. 引数依存のルックアップを修正する提案がいくつかありましたが、C++ 標準委員会によって受け入れられたものはありません。