名前空間
変種
操作

実引数依存の名前探索

From cppreference.com
< cpp‎ | language
 
 
C++言語
全般
フロー制御
条件実行文
if
繰り返し文 (ループ)
for
範囲for (C++11)
ジャンプ文
関数
関数宣言
ラムダ式
inline指定子
動的例外仕様 (C++17まで*)
noexcept指定子 (C++11)
例外
名前空間
指定子
const/volatile
decltype (C++11)
auto (C++11)
constexpr (C++11)
consteval (C++20)
constinit (C++20)
記憶域期間指定子
初期化
代替表現
リテラル
ブーリアン - 整数 - 浮動小数点数
文字 - 文字列 - nullptr (C++11)
ユーザー定義 (C++11)
ユーティリティ
属性 (C++11)
typedef宣言
型エイリアス宣言 (C++11)
キャスト
メモリ確保
クラス
クラス固有の関数プロパティ
explicit (C++11)
static

特殊メンバ関数
テンプレート
その他
 
 

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

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

#include <iostream>
 
int main()
{
    std::cout << "Test\n"; // There is no operator<< in global namespace, but ADL
                           // examines std namespace because the left argument is in
                           // std and finds std::operator<<(std::ostream&, const char*)
    operator<<(std::cout, "Test\n"); // Same, using function call notation
 
    // However,
    std::cout << endl; // Error: “endl” is not declared in this namespace.
                       // This is not a function call to endl(), so ADL does not apply
 
    endl(std::cout); // OK: this is a function call: ADL examines std namespace
                     // because the argument of endl is in std, and finds std::endl
 
    (endl)(std::cout); // Error: “endl” is not declared in this namespace.
                       // The sub-expression (endl) is not an unqualified-id
}

目次

[編集] 詳細

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

1) クラスメンバーの宣言。
2) ブロックスコープでの関数宣言(using 宣言ではない)。
3) 関数または関数テンプレートではない宣言(例:関数オブジェクト、または探索中の関数名と競合する名前を持つ別の変数)。

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

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

関連するクラスと名前空間のセット内のいずれかの名前空間がインライン名前空間である場合、その囲む名前空間もセットに追加されます。

関連するクラスと名前空間のセット内のいずれかの名前空間が直接インライン名前空間を含んでいる場合、そのインライン名前空間がセットに追加されます。

(C++11以降)

関連するクラスと名前空間のセットが決定された後、このセットのクラスで見つかったすべての宣言は、以下の点2で述べられている名前空間スコープのフレンド関数および関数テンプレートを除き、ADLのさらなる処理のために破棄されます。

通常の非修飾名前探索によって見つかった宣言のセットと、ADLによって生成された関連するセットのすべての要素で見つかった宣言のセットは、以下の特別なルールに従ってマージされます。

1) 関連する名前空間のusing ディレクティブは無視されます。
2) 関連するクラスで宣言された名前空間スコープのフレンド関数(および関数テンプレート)は、通常の探索では見つからなくてもADLを通じて可視になります。
3) 関数および関数テンプレート以外のすべての名前は無視されます(変数との衝突はありません)。

[編集] 備考

引数依存の名前探索のため、クラスと同じ名前空間で定義された非メンバー関数および非メンバー演算子は、そのクラスの公開インターフェースの一部と見なされます(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 名前空間の型を操作する演算子をグローバル名前空間またはユーザー定義名前空間で宣言することは非現実的です。例えば、std::vectorstd::pair のためのカスタムの operator>>operator+ など(ただし、vector/pair の要素型がユーザー定義型であり、その名前空間がADLに追加される場合は除く)。そのような演算子は、標準ライブラリのアルゴリズムなどのテンプレートインスタンス化からは探索されません。詳細については、依存名を参照してください。

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

template<typename T>
struct number
{
    number(int);
    friend number gcd(number x, number y) { return 0; }; // Definition within
                                                         // a class template
};
 
// Unless a matching declaration is provided gcd is
// an invisible (except through ADL) member of this namespace
void g()
{
    number<double> a(3), b(4);
    a = gcd(a, b); // Finds gcd because number<double> is an associated class,
                   // making gcd visible in its namespace (global scope)
//  b = gcd(3, 4); // Error; gcd is not visible
}

通常の探索で何も見つからなくてもADLを通じて関数呼び出しが解決されることがありますが、明示的に指定されたテンプレート引数を持つ関数テンプレートへの関数呼び出しは、通常の探索で見つかるテンプレートの宣言が必要です(そうでない場合、不明な名前の後に小なり文字が続くのは構文エラーです)。

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 non-type 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のみの探索(つまり、関連する名前空間のみでの探索)が行われます。

  • メンバー探索が失敗した場合の範囲ベース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); // Calls B::f (endless recursion)
    }
 
    void g(A::X x)
    {
        g(x); // Error: ambiguous between B::g (ordinary lookup)
              //        and A::g (argument-dependent lookup)
    }
 
    void h(A::Y y)
    {
        h(y); // Calls B::h (endless recursion): ADL examines the A namespace
              // but finds no A::h, so only B::h from ordinary lookup is used
    }
}

[編集] 欠陥報告

以下の動作変更を伴う欠陥報告が、以前に公開されたC++標準に遡って適用されました。

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

[編集] 関連項目

[編集] 外部リンク

  1. Andrew Koenig: "A Personal Note About Argument-Dependent Lookup"
  2. H. Sutter (1998) "What's In a Class? - The Interface Principle" in C++ Report, 10(3)
English 日本語 中文(简体) 中文(繁體)