実引数依存の名前探索

出典: フリー百科事典『ウィキペディア(Wikipedia)』

実引数依存の名前探索じつひきすういぞんのなまえたんさくADL)とは、C++において関数呼出時に与えられた引数の型に依存して、呼び出す関数を探索 (lookup)する仕組みのことである。英語ではKoenig lookupargument dependent lookup (ADL)、argument dependent name lookupなどと呼ばれる。なお、Koenig lookupとは、この仕組みをAndrew Koenigが提案したことにちなむ。


概要[編集]

ADLでは、探索される名前空間は実引数に依存する。A型のオブジェクトが関数呼出の際に実引数として用いられると、Aに関連する名前空間(Aが含まれる名前空間とAの基底クラスが含まれる名前空間の和集合)からその関数が探索される。 探索の後、見つかった宣言の集合の中から多重定義の解決が行われる。 以下に例を示す。

namespace SomeSpace
{
    class A {};
    void f(A) {}
}
int main()
{
    SomeSpace::A a;
    f(a); // SomeSpace::f(a);と書かずとも、SomeSpace::fが呼ばれる。

    return 0;
}

標準C++ライブラリでは、ADLを主に演算子多重定義関数に対して用いている。たとえば次のプログラムはADLが無ければコンパイルできない。

#include <iostream>
#include <string>

int main()
{
    std::string msg = "Hello World, where did operator<<() come from?";
    std::cout << msg << std::endl;

    return 0;
}

std::ostream& std::operator <<(std::ostream&, const std::string&)と宣言された関数は、ADLによって見付かる(この関数はstd名前空間の中に存在することに注目)。ところで、std::endlは関数であるが、operator <<の引数として用いられているため、std::などといった完全な修飾が必要であることに注意。

インタフェース[編集]

C++ユーザからは、ADLで見つかる名前はクラスのインタフェースの一部と扱われる。

Standard Template Library (STL)では、一部のアルゴリズム関数がswap関数を修飾無しで呼んでいる。この場合、ADLで何も見つからなければ、stdのswap関数が呼ばれるが、ADLで見つかったときはそちらが呼ばれる。例えば、ある名前空間NSにクラスFooと関数swap(Foo&, Foo&)が定義されていると、アルゴリズム関数はNS::swap(Foo&, Foo&)を使用する。ただし、この挙動はC++03では規定されておらず、必ずしもそうなるとは限らない。C++0xで規定される見込みである。

問題点[編集]

ADLは自由関数(クラスのメンバ関数でない関数)もクラスのインタフェースとして扱う。つまり、名前空間に制限をもたらし、ADLの必要がなければ完全に修飾された名前を用いる必要があることを意味する。逆の例として、標準C++ライブラリは2つの値の交換にstd::swapを修飾なしで呼ぶことが挙げられる。

別の案として、std::swapをユーザに多重定義させるという方法がある。次のコードは挙動が異なる。

完全修飾した名前で呼ぶ場合:

std::swap(a, b);

予めusingしておく場合:

using std::swap;
swap(a, b);

ただし、aとbはN::Aという型とする。

N::swap(N::A&, N::A&)が存在した場合、後者ではそれが呼ばれるが、前者では呼ばれない。さらに細かいことを言えば、仮に両方とも定義されていたら、前者ではstd::swap(N::A&, N::A&)が呼ばれるが、後者ではどちらにするか曖昧になる(名前の探索に失敗しコンパイルエラーになる)。

なお、std::swapを特殊化するという方法もあるが、特殊化しようとする型がテンプレートの場合に対応できない(自由関数で部分特殊化はできない)ために完璧ではない[1]

なお、std名前空間での多重定義は現在認められていない(特殊化は認められている)。

一般にADLに過度に依存すると意味の問題が起こる。あるライブラリL1がL1::foo(T)が呼び出される前提で未修飾のfoo(T)という呼出を行っているとする。別のライブラリL2も同様にfoo(T)の呼出を行っている。2つのライブラリを同時に使うと、L1::foo(T)が呼ばれなければならない場面でL2::foo(T)が呼ばれるなど互いに意図するとおりにならない可能性が生じる。しかし、L1が内部でL1::foo(T)とし、L2も内部で同様にL2::foo(T)と共に完全に修飾していれば、このようなADLの心配は全く起こらない(逆にADLを起こしたい場合はusing L1::foo; foo(x);のように書く)。

脚注[編集]

  1. ^ Cryolite (2004年9月2日). “swapの特殊化・その他,細かいこと”. Cry's Diary. 2009年2月1日閲覧。

外部リンク[編集]