名前空間
変種
操作

実引数依存の名前探索

提供: cppreference.com
< cpp‎ | language
 
 
C++言語
一般的なトピック
フロー制御
条件付き実行文
繰り返し文 (ループ)
ジャンプ文
関数
関数宣言
ラムダ関数宣言
inline 指定子
例外指定 (C++20未満)
noexcept 指定子 (C++11)
例外
名前空間
指定子
decltype (C++11)
auto (C++11)
alignas (C++11)
記憶域期間指定子
初期化
代替表現
リテラル
ブーリアン - 整数 - 浮動小数点
文字 - 文字列 - nullptr (C++11)
ユーザ定義 (C++11)
ユーティリティ
属性 (C++11)
typedef 宣言
型エイリアス宣言 (C++11)
キャスト
暗黙の変換 - 明示的な変換
static_cast - dynamic_cast
const_cast - reinterpret_cast
メモリ確保
クラス
クラス固有の関数特性
特別なメンバ関数
テンプレート
その他
 
 

実引数依存の名前探索 (ADL または Koenig lookup とも言います) は、関数呼び出し式 (オーバーロードされた演算子に対する暗黙の関数呼び出しを含みます) における名前探索のためのルールの集合です。 これらの関数名は、通常の非修飾名の名前探索によって考慮されるスコープおよび名前空間に加えて、その実引数の名前空間でも名前探索されます。

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

#include <iostream>
int main()
{
    std::cout << "Test\n"; // operator<< はグローバル名前空間にはありませんが、
                           // 左の引数が std 名前空間であるため、 ADL によって std が調べられ、
                           // std::operator<<(std::ostream&, const char*) が発見されます。
    operator<<(std::cout, "Test\n"); // 同上。 関数呼び出し表記を使用。
 
    // しかし、
    std::cout << endl; // エラー。 「endl」はこの名前空間では宣言されていません。
                       // これは endl の関数呼び出しではないため、 ADL は適用されません。
 
    endl(std::cout); // OK。 これは関数呼び出しです。 endl の引数が std 名前空間であるため、
                     // ADL によって std 名前空間が調べられ、 std::endl が発見されます。
 
    (endl)(std::cout); // エラー。 「endl」はこの名前空間では宣言されていません。
                       // 部分式 (endl) は関数呼び出し式ではありません。
}


目次

[編集] 詳細

まず、通常の非修飾名の名前探索によって生成される名前探索集合が以下のいずれかを含む場合、実引数依存の名前探索は考慮されません。

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) 実引数がオーバーロードされた関数 (または関数テンプレート) の集合に対する名前または adderess-of 式の場合、そのオーバーロード集合内のすべての関数が調べられ、それらの関連するクラスと名前空間の集合が集合に追加されます。
a) さらに、そのオーバーロード集合が template-id によって表される場合は、そのすべての型テンプレート実引数およびテンプレートテンプレート実引数 (非型テンプレート引数は含まれません) が調べられ、それらの関連するクラスと名前空間の集合が集合に追加されます。

関連するクラスと名前空間の集合に含まれる名前空間がインライン名前空間の場合は、その囲っている名前空間も集合に追加されます。

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

関連するクラスと名前空間の集合が決定された後、その集合のクラス内に発見されるすべての宣言が、下の第2項で述べるように名前空間スコープのフレンド関数および関数テンプレートを除いて、 ADL の目的に対しては破棄されます。

通常の非修飾名の名前探索によって発見された宣言の集合と、 ADL によって生成された関連する集合のすべての要素内に発見された宣言の集合が、以下の特別なルールを用いて、マージされます。

1) 関連する名前空間内の using 指令は無視されます。
2) 関連するクラス内に宣言されている名前空間スコープのフレンド関数 (および関数テンプレート) は、たとえ通常の名前探索によって可視でなくとも、 ADL によって可視になります。
3) 関数および関数テンプレートのものを除くすべての名前は無視されます (変数と衝突しません)。

[編集] ノート

実引数依存の名前探索のために、クラスと同じ名前空間に定義されている非メンバ関数および非メンバ演算子はそのクラスのパブリックインタフェースの一部であるとみなされます (それらが ADL を通して発見される場合)[1]

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; }; // クラステンプレート内の
                                                         // 定義
};
// 対応する宣言が提供されない限り、 gcd は不可視です (ADL を通した場合を除いて)。
// この名前空間のメンバ
void g() {
    number<double> a(3), b(4);
    a = gcd(a,b); // number<double> が関連するクラスであり、 gcd をその名前空間
                  // (グローバルスコープ) 内で可視にするため、 gcd を発見します。
//  b = gcd(3,4); // エラー、 gcd は可視ではありません。
}

関数呼び出しは、たとえ通常の名前探索が何も発見しない場合でも 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);      // C++20 未満は構文エラー (非修飾名の名前探索は f を発見しません)。
  N1::f<3>(s);  // OK、修飾名の名前探索はテンプレート「f」を発見します。
  N2::f<3>(s);  // エラー、 N2::f は非型引数を取りません。
                //        ADL は非修飾名を用いたときしか動作しないため、
                //        N1::f は名前探索されません。
  using N2::f;
  f<3>(s); // OK、非修飾名の名前探索は今回は N2::f を発見します。
           //     その後、この名前が非修飾であるため ADL が作動し、
           //     N1::f を発見します。
}
(C++20未満)

以下の文脈では、 ADL のみの名前探索 (つまり、関連する名前空間内のみでの名前探索) が行われます。

  • 範囲 for でメンバの名前探索が失敗した場合に行われる非メンバ関数 begin および end の名前探索。
  • テンプレートの実体化時点の依存名の名前探索
  • タプルライクな型に対する構造化束縛宣言によって行われる非メンバ関数 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 のみが使用されます。
    }
}


[編集] 関連項目

[編集] 参考文献

  1. H. Sutter (1998) "What's In a Class? - The Interface Principle" in C++ Report, 10(3)