Namespaces
Variants

Argument-dependent lookup

From cppreference.net
C++ language
General topics
Flow control
Conditional execution statements
Iteration statements (loops)
Jump statements
Functions
Function declaration
Lambda function expression
inline specifier
Dynamic exception specifications ( until C++17* )
noexcept specifier (C++11)
Exceptions
Namespaces
Types
Specifiers
constexpr (C++11)
consteval (C++20)
constinit (C++20)
Storage duration specifiers
Initialization
Expressions
Alternative representations
Literals
Boolean - Integer - Floating-point
Character - String - nullptr (C++11)
User-defined (C++11)
Utilities
Attributes (C++11)
Types
typedef declaration
Type alias declaration (C++11)
Casts
Memory allocation
Classes
Class-specific function properties
Special member functions
Templates
Miscellaneous

実引数依存の名前探索 (ADL)、別名 Koenig lookup [1] 、は 関数呼び出し式 における非修飾関数名を探索するための規則群であり、 オーバーロードされた演算子 への暗黙的な関数呼び出しも含みます。これらの関数名は、通常の 非修飾名前探索 で考慮されるスコープと名前空間に加えて、実引数の名前空間でも探索されます。

実引数依存の名前探索(ADL)により、異なる名前空間で定義された演算子を使用することが可能になります。例:

#include <iostream>
int main()
{
    std::cout << "Test\n"; // グローバル名前空間にはoperator<<は存在しないが、ADLにより
                           // 左側の実引数がstd名前空間にあるためstd名前空間を調査し、
                           // std::operator<<(std::ostream&, const char*)を見つける
    operator<<(std::cout, "Test\n"); // 同じ処理を関数呼び出し記法で記述
    // しかしながら、
    std::cout << endl; // エラー: 「endl」はこの名前空間で宣言されていない
                       // これはendl()への関数呼び出しではないため、ADLは適用されない
    endl(std::cout); // OK: これは関数呼び出しであるため、ADLがstd名前空間を調査
                     // endlの実引数がstd名前空間にあるため、std::endlを見つける
    (endl)(std::cout); // エラー: 「endl」はこの名前空間で宣言されていない
                       // 部分式(endl)は非修飾名ではない
}

目次

詳細

まず、通常の 非修飾名探索 によって生成された探索セットが以下のいずれかを含む場合、引数依存探索は考慮されません:

1) クラスメンバーの宣言。
2) ブロックスコープでの関数宣言(これは using declaration ではありません)。
3) 関数または関数テンプレートではない宣言(例えば、関数オブジェクトや、ルックアップ対象の関数名と競合する他の変数など)。

それ以外の場合、関数呼び出し式の各引数について、その型が調べられ、ルックアップに追加される 関連する名前空間とクラスの集合 が決定されます。

1) 基本型の引数に対しては、関連する名前空間とクラスの集合は空です。
2) クラス型(共用体を含む)の引数に対して、集合は以下で構成される:
a) クラス自体。
b) クラスが complete である場合、そのすべての直接および間接基底クラス。
c) クラスが 他のクラスのメンバー である場合、それがメンバーであるクラス。
d) セットに追加されたクラスの最も内側の外側の名前空間。
3) 引数の型が class template の特殊化である場合、クラスの規則に加えて、以下の関連クラスと名前空間が集合に追加されます。
a) 型テンプレートパラメータに提供されるすべてのテンプレート引数の型(定数テンプレートパラメータとテンプレートテンプレートパラメータはスキップ)。
b) 任意のテンプレートテンプレート引数がメンバーである名前空間。
c) テンプレートテンプレート引数がメンバーであるクラス(それらがクラスメンバーテンプレートである場合)。
4) 列挙型の引数に対しては、その列挙型の宣言が定義されている最も内側の外側名前空間が集合に追加されます。列挙型がクラスのメンバである場合、そのクラスが集合に追加されます。
5) T へのポインタ、または T の配列へのポインタ型の引数に対しては、型 T が調べられ、それに関連付けられたクラスと名前空間の集合が集合に追加されます。
6) 関数型の引数に対しては、関数パラメータの型と関数戻り値の型が検査され、それらに関連付けられたクラスと名前空間の集合が集合に追加されます。
7) メンバ関数ポインタ型 F の引数について、 X クラスのメンバ関数ポインタ型 F の関数パラメータ型、関数戻り値型、およびクラス X が検査され、それらに関連するクラスと名前空間の集合が集合に追加されます。
8) データメンバへのポインタ型の引数に対して、 T クラス X のメンバ型と型 X の両方が調べられ、それらに関連するクラスと名前空間の集合が集合に追加されます。
9) 引数が オーバーロードされた関数の集合 (または関数テンプレート)の名前またはアドレス取得式である場合、オーバーロード集合内のすべての関数が検査され、関連するクラスと名前空間の集合がセットに追加されます。
  • さらに、オーバーロード集合が テンプレート識別子 によって指定されている場合、そのすべての型テンプレート引数とテンプレートテンプレート引数(ただし定数テンプレート引数は除く)が検査され、関連するクラスと名前空間の集合がセットに追加されます。

関連するクラスと名前空間の集合内のいずれかの名前空間が inline namespace である場合、その外側の名前空間も集合に追加されます。

関連するクラスと名前空間の集合内のいずれかの名前空間が直接inline namespaceを含む場合、そのinline namespaceは集合に追加されます。

(C++11以降)

関連するクラスと名前空間の集合が決定された後、この集合のクラス内で見つかったすべての宣言は、以下のポイント2で述べられているように、名前空間スコープのフレンド関数と関数テンプレートを除き、それ以降のADL処理の目的では破棄されます。

通常の unqualified lookup によって見つかった宣言の集合と、ADLによって生成された関連集合の全要素で見つかった宣言の集合は、以下の特別な規則に従って統合されます:

1) using directives 関連付けられた名前空間内のusing directivesは無視されます。
2) 関連するクラスで宣言された名前空間スコープのフレンド関数(および関数テンプレート)は、通常のルックアップでは可視でなくても、ADLを通じて可視となります。
3) 関数および関数テンプレートを除くすべての名前は無視されます(変数との衝突は発生しません)。

注記

実引数依存の名前探索(ADL)により、クラスと同じ名前空間で定義された非メンバ関数および非メンバ演算子は、そのクラスの公開インターフェースの一部と見なされます(ADLを通じて発見された場合) [2]

ADLは、ジェネリックコードで2つのオブジェクトを交換する確立されたイディオムの背景にある理由です: using std:: swap ; swap ( obj1, obj2 ) ; なぜなら直接 std:: swap ( obj1, obj2 ) を呼び出すと、 obj1 obj2 の型と同じ名前空間で定義されている可能性のあるユーザー定義の swap() 関数が考慮されず、修飾なしの swap ( obj1, obj2 ) を呼び出すだけでは、ユーザー定義のオーバーロードが提供されていない場合には何も呼び出されないからです。特に、 std::iter_swap および他のすべての標準ライブラリアルゴリズムは、 Swappable な型を扱う際にこのアプローチを使用します。

名前探索の規則により、グローバル名前空間やユーザー定義名前空間で、 std 名前空間の型を操作する演算子(例えば、 operator >> operator + std::vector std::pair に対して宣言すること)は非現実的です(ただし、vector/pairの要素型がユーザー定義型である場合は除きます。その場合、ADLにそれらの名前空間が追加されます)。このような演算子は、標準ライブラリのアルゴリズムなどのテンプレートインスタンス化からは探索されません。詳細は dependent names を参照してください。

ADLは、 friend function (通常はオーバーロードされた演算子)を、名前空間レベルで宣言されていなかった場合でも、クラスまたはクラステンプレート内で完全に定義されているものを見つけることができます。

template<typename T>
struct number
{
    number(int);
    friend number gcd(number x, number y) { return 0; }; // クラステンプレート内での定義
                                                         // 
};
// 一致する宣言が提供されない限り、gcdはこの名前空間の
// (ADLを除いて)不可視のメンバーとなる
void g()
{
    number<double> a(3), b(4);
    a = gcd(a, b); // number<double>が関連クラスであるためgcdを発見し、
                   // その名前空間(グローバルスコープ)で可視となる
//  b = gcd(3, 4); // エラー; gcdは可視ではない
}

通常のルックアップで何も見つからなくてもADLを通じて関数呼び出しが解決される場合があるが、明示的にテンプレート引数を指定した function template への関数呼び出しでは、通常のルックアップによってテンプレートの宣言が見つかる必要がある(そうでない場合、未知の名前に続いて小なり記号が現れるのは構文エラーとなる)。

namespace N1
{
    struct S {};
    template<int X>
    void f(S);
}
namespace N2
{
    template<class T>
    void f(T t);
}
void g(N1::S s)
{
    f<3>(s);     // Syntax error until C++20 (unqualified lookup finds no f)
    N1::f<3>(s); // OK, qualified lookup finds the template 'f'
    N2::f<3>(s); // Error: N2::f does not take a constant parameter
                 //        N1::f is not looked up because ADL only works
                 //              with unqualified names
    using N2::f;
    f<3>(s); // OK: Unqualified lookup now finds N2::f
             //     then ADL kicks in because this name is unqualified
             //     and finds N1::f
}
(C++20まで)

以下の状況では、ADLのみのルックアップ(つまり、関連付けられた名前空間内のみでのルックアップ)が行われます:

  • メンバー検索が失敗した場合、 range-for ループによって実行される非メンバー関数 begin および end の検索。
(C++11以降)
  • タプルライク型に対する 構造化束縛宣言 によって実行される非メンバー関数 get のルックアップ。
(C++17 以降)

http://www.gotw.ca/gotw/030.htm からの例

namespace A
{
    struct X;
    struct Y;
    void f(int);
    void g(X);
}
namespace B
{
    void f(int i)
    {
        f(i); // B::fを呼び出す(無限再帰)
    }
    void g(A::X x)
    {
        g(x); // エラー: B::g(通常の名前探索)とA::g(実引数依存探索)の間で曖昧
    }
    void h(A::Y y)
    {
        h(y); // B::hを呼び出す(無限再帰): ADLはA名前空間を調査するが
              // A::hが見つからないため、通常の名前探索からのB::hのみが使用される
    }
}

不具合報告

以下の動作変更の欠陥報告書は、以前に公開されたC++規格に対して遡及的に適用されました。

DR 適用対象 公開時の動作 正しい動作
CWG 33 C++98 ルックアップに使用される引数がオーバーロードされた関数群のアドレスまたは関数テンプレートの場合、
関連する名前空間またはクラスが未規定
規定
CWG 90 C++98 ネストされた非共用体クラスの関連クラスにその外側のクラスが含まれていなかったが、
ネストされた共用体はその外側のクラスと関連していた
非共用体も関連付けられる
CWG 239 C++98 通常の非修飾名探索で見つかったブロックスコープの関数宣言が
ADLの発生を妨げなかった
using 宣言を除いて
ADLは考慮されない
CWG 997 C++98 関数テンプレートの関連クラスと名前空間を決定する際に、
依存するパラメータ型と戻り値の型が考慮から除外されていた
含まれる
CWG 1690 C++98
C++11
ADLは返されるラムダ(C++11)またはローカルクラス型の
オブジェクト(C++98)を見つけることができなかった
見つけることができる
CWG 1691 C++11 ADLが不透明な列挙型宣言に対して驚くべき動作を示した 修正
CWG 1692 C++98 二重にネストされたクラスには関連する名前空間がなかった
(それらの外側のクラスはどの名前空間のメンバーでもない)
関連する名前空間は
最も内側の外側の
名前空間まで拡張される
CWG 2857 C++98 不完全なクラス型の関連クラスに
その基底クラスが含まれていた
含まれない

関連項目

外部リンク

  1. Andrew Koenig: "引数依存の名前探索に関する個人的な覚書"
  2. H. Sutter (1998) "クラスには何があるか? - インターフェース原則" in C++ Report, 10(3)