依賴於實參的名字查找

依賴於實參的名字查找是C++程序設計語言中的名字查找機制之一。英文為「argument-dependent lookup」，因此縮寫為ADL。ADL依據函數調用中的實參的數據類型查找未限定（unqualified）的函數名（或者函數模板名）。這也被稱作「克尼格查找」（Koenig lookup），雖然安德魯·克尼格並不是它的發明者。

## 語義

### 前置基礎

兩個關鍵概念：

- 普通未限定名字查找：C++ 基礎的名字查找規則，從當前作用域 → 外層作用域 → 全局作用域順序找名字，不考慮函數實參的類型 / 命名空間。
- ADL（依賴於實參的名字查找）：補充查找規則，根據函數實參的類型 / 所屬命名空間，去對應命名空間裡找函數名，僅用於未限定函數調用（如 func(a)，不是 ns::func(a)）。

### 依賴於實參的名字查找的先決條件

如果普通未限定（unqualified）名字查找所產生的候選集包括下述情形，則不會啟動依賴於實參的名字查找：

- 類成員聲明（此種情形僅指普通的類成員函數，不指類成員運算符函數）。即普通未限定查找找到了類的非運算符成員函數（比如類里的 void print），ADL 不啟動。
- 塊作用域內的函數的聲明，不含(using-declaration)
- 任何不是函數或者函數模板的聲明（例如函數對象或者另一個變量其名字與被查詢的函數名字衝突）

上述三條規則反過來，就是ADL能夠啟動的唯一前提：

- 普通未限定名字查找沒有找到普通類成員函數。
- 普通未限定名字查找沒有找到塊作用域內的函數聲明。
- 普通未限定名字查找沒有找到任何非函數 / 非函數模板的聲明。

### 命名空間與類的相關集合

函數調用表達式的每個實參的類型用於確定命名空間與類的相關集合（associated set of namespaces and classes）並用於函數名字查找：

- 基本類型（fundamental type）實參的命名空間與類的相關集合為空。
- 類類型（class type，指struct, class, union類型）, 相關集合包括
- 類類型自身
- 如果類類型是另一個類的成員，則那個包含了類類型的類。
- 該類型的所有的直接或間接基類。
- 該類類型的所有相關類的最內部包含命名空間。
- 如果實參是類模板特化後得到的類型，除了應用類類型規則，還要包括下述規則提及的類型的相關命名空間與類的集合：
- 類型模板形參（type template parameter）所對應的模板實參的類型，不包括非類型的模板形參、模板的模板形參。
- 模板的模板實參（template template argument）所在的命名空間。
- 模板的模板實參所在的類（如果這個類包含了這個成員模板）。
- 對於枚舉類型的實參，枚舉類型所在的命名空間；如果枚舉類型是一個類的成員類型，則該類加入相關集合。
- 如果實參是類型T的指針或者是類型T的陣列的指針，則類型T的相關集合被加入該實參類型的相關集合。
- 如果實參是函數類型，那麼該函數的形參類型與函數返回值的類型的相關集合被加入到該實參類型的相關集合。
- 如果實參是類X的成員函數F的類成員函數指針，那麼該成員函數的形參類型、該成員函數返回值的類型、該成員函數所屬類X的相關集合都被加入到該實參類型的相關集合。
- 如果實參是類X的數據成員T的類數據成員指針，那麼該成員類型、該數據成員所屬類X的相關集合都被加入到該實參類型的相關集合。
- 如果實參是一套重載函數（或者函數模板）的名字或者取地址表達式(address-of expression)，那麼重載集中的每個函數的相關集合都被加入到該實參類型的相關集合。
- 此外，如果重載集是template-id (模板名字與模板實參), 則所有的類型模板實參與模板的模板實參（不含非類型模板實參）的相關集合都被加入到該實參類型的相關集合。
- 如果相關集合中的任何命名空間是內聯命名空間（inline namespace）, 則包含它的命名空間被增加到相關集合中。
- 如果相關集合中的一個命名空間直接包含了內聯命名空間，則內聯命名空間被增加到相關集合中。
- 相關命名空間中的using-directives被忽略

通常的未限定查找發現的結果與ADL查找發現的結果應該合併，並遵從如下特別規則：

- 通過ADL查找到的相關類內的友函數與函數模版是可見的
- 所有的名字，如果不是函數或者函數模版，將被忽略（不與變量名字衝突）

## 例子

一個簡單示例：

```cpp
namespace NS {
   class A {};
   void f(A *&, int) {}
}

int main() 
{
   NS::A *a;
   f(a, 0);    //calls NS::f
   (f)(a,0);     //error: NS::f not considered; parentheses prevent argument-dependent lookup
}
```

更為複雜、精緻的例子：

```cpp
namespace N2 { struct DS; }

namespace N1 {
    struct S {
        void operator+(S) {}
    };
    template<int X> void f(S) {}
    void fDS(N2::DS* v) {}
}

namespace N2 {
    struct DS :N1::S {};

    template<class T> void f(T t) {}
}

void g() {	 
    N2::DS sv;
    fDS(&sv);    // sv的类型N2::DS的基类型N1::S所在的命名空间N1的函数N1::fDS
    sv+sv;       // 调用N1::S::operator+(S)运算符成员函数
}
```

另一個示例：

```cpp
namespace N1 {
    struct S {};
    template<int X> void f(S) {}
    void bar(S) {}
}

namespace N2 {	 
    template<class T> void f(T t) {}
}

void g(N1::S s) { 	
    bar(s);         // lookup N1::bar
    // f<3>(s);     // Syntax error: unqualified lookup finds no f, so it understands as arithematic expression " f < 3 "
    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
}
```

關於內聯命名空間的示例：

```cpp
namespace ADL{
    inline namespace v101 {              // 下述代码中，命名空间名字v101都可以忽略
        class foo {
        public:
            class bar { };
        };
    }
    void func1(foo::bar v) {
        int i = 101;
    }
}
 
int main()
{
    ADL::foo::bar v1;
    func1(v1);               // lookup to ADL::func1  
}
```

## 接口

ADL能查詢到的函數被認為是類的接口之一。標準模板庫的幾個`std`命名空間的演算法使用未限定的`swap`調用。使用時，如果名字查找沒找到其它結果，則使用`std::swap`函數；如果這些演算法使用第三方類，如`Foo`，在另一個命名空間中發現包含了`swap(Foo&, Foo&)`, 則重載版的`swap`被優先使用。

C++標準程式庫常見模式是用ADL查找程序聲明的重載運算符。例如，下述程序如果沒有ADL將無法編譯通過：

```cpp
#include<iostream>

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

運算符`<<`等價於調用函數`operator<<`，但沒有給出`std`限定符。通過ADL查找到函數`std::ostream& std::operator<<(std::ostream&, const char*)`。

## 批評

ADL使得類之外定義的函數就如同類的接口一樣被調用，這使得命名空間不是過於嚴格。例如，C++標準模板庫使用未限定的`swap`調用，允許用戶定義自己的`swap`供標準模板庫的演算法使用。換句話說。

```cpp
std::swap(a, b);
```

效果可能相同與或不同於

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

（其中`a`與`b`的類型是`N::A`）。因為如果`N::swap(N::A&, N::A&)`存在，上述第二段代碼將調用它，而上述第一段代碼將不調用它。

## 外部連結

- Argument-dependent lookup
- What's In a Class? - The Interface Principle by Herb Sutter
- Namespaces and the Interface Principle by Herb Sutter
- Why I Hate Namespaces by Danny Kalev
- "A Modest Proposal: Fixing ADL (revision 2)" by Herb Sutter