天天看點

參數依賴查找(ADL,Argument-dependent lookup)

參數依賴查找(Argument-dependent lookup),又稱 ADL 或 Koenig 查找,是一組于​​函數調用表達式​​查找非限定函數名的規則,包含對​​重載運算符​​的隐式函數調用。在通常​​非限定名稱查找​​所考慮的作用域和命名空間之外,還在其參數的命名空間中查找這些函數。

參數依賴查找使使用定義于不同命名空間的運算符可行。例如:

1 #include <iostream>
 2 int main()
 3 {
 4     std::cout << "Test\n"; // 全局命名空間無 operator<< ,但 ADL 檢驗 std 命名空間,
 5                            // 因為左參數在 std 命名空間中
 6                            // 并找到 std::operator<<(std::ostream&, const char*)
 7     operator<<(std::cout, "Test\n"); // 同上,用函數調用記法
 8  
 9     // 然而,
10     std::cout << endl; // 錯誤: 'endl' 不聲明于此命名空間。
11                        // 此非對 endl() 的函數調用,故不應用 ADL
12  
13     endl(std::cout); // OK :這是函數調用: ADL 檢驗 std 命名空間,
14                      // 因為 endl 的參數在 std ,并找到 std::endl
15  
16     (endl)(std::cout); // 錯誤: 'endl' 不聲明于此命名空間。
17                        // 子表達式 (endl) 不是函數調用表達式
18 }      

細節

首先,若通常​​非限定查找​​所生成的集合含有下列任何内容,則不考慮參數依賴查找:

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) 若參數是​​重載函數集的取址表達式​​(或對函數模闆)的名稱,則檢驗重載集中的每個元素,并添加其類與命名空間的關聯集到集合。

a) 另外,若重載集為模闆 id (帶模闆實參的模闆名)所指名,則檢驗其所有類型模闆實參與模闆模闆實參(但無非類型模闆實參),并添加其類與命名空間的關聯集到集合。

若類與命名空間的關聯集中的任何命名空間是​​内聯命名空間​​,則添加其外圍命名空間到集合。

若類與命名空間的關聯集中的任何命名空間直接含有内聯命名空間,則添加該内聯命名空間到集合。

在确定命名空間與類的關聯集後,為了進一步的 ADL 處理,忽略此集中所有于類中找到的聲明,除了命名空間作用域的友元函數及函數模闆,陳述于後述點 2 。

以下列特殊規則,合并普通​​非限定查找​​找到的聲明集合,與在 ADL 所生成關聯集的所有元素中找到的聲明集合

1) 忽略關聯命名空間中的 ​​using 指令​​

2) 聲明于關聯類中的命名空間作用域友元函數(及函數模闆)通過 ADL 可見,即使它們通過普通查找不可見。

3) 忽略函數與函數模闆外的所有名稱(與變量不沖突)

注意

因為參數依賴查找,定義于相同命名空間的非成員函數和非成員運算符被認為是該類公開接口的一部分(若它們為 ADL 所找到)​​[1]​​。

ADL 是為于泛型代碼交換二個對象而建立的手法的背後理由:

using std::swap;
swap(obj1, obj2);      

因為直接調用 ​​std::swap​​(obj1, obj2) 不會考慮使用者定義的 swap() 函數,它可能定義于與 obj1 或 obj2 類型之定義相同的空間,而僅調用非限定的 swap(obj1, obj2) 會無法調用任何函數,若不提供使用者定義重載。特别是 std::iter_swap 與所有其他标準庫算法在處理​​可交換​​ (​

​Swappable​

​) 類型時使用此手段。

名稱查找規則使得在來自 std 命名空間的類型上聲明運算符于全局或使用者定義命名空間,例如對于 std::vector 或 std::pair 的自定義 operator+ 或 operator>> 不适于實踐(除非 vector/pair 的元素類型是使用者定義類型,這會添加其命名空間到 ADL )。這種運算符不會從諸如标準庫算法的模闆執行個體化查找。進一步細節見​​依賴名​​。

ADL 能找到全體定義于類或類模闆内的​​友元函數​​(典型地是重載的運算符),即使它完全不在命名空間層次聲明。

1 template<typename T>
 2 struct number
 3 {
 4     number(int);
 5     friend number gcd(number x, number y) { return 0; }; // 類模闆内的定義
 6 };
 7 // 除非提供比對聲明,否則 gcd 是此命名空間的不可見成員(除非通過 ADL )
 8 void g() {
 9     number<double> a(3), b(4);
10     a = gcd(a,b); // 找到 gcd ,因為 number<double> 是關聯類,
11                   // 令 gcd 于其命名空間(全局命名空間)可見
12 //  b = gcd(3,4); // 錯誤: gcd 不可見
13 }      

盡管即使普通查找找不到結果,函數調用也能通過 ADL 解決,對帶顯示指定模闆實參的​​函數模闆​​調用還是要求有普通查找所能找到的模闆聲明(否則,它會是遇到未知名稱後随小于号的文法錯誤)

1 namespace N1 {
 2   struct S {};
 3   template<int X> void f(S);
 4 }
 5 namespace N2 {
 6   template<class T> void f(T t);
 7 }
 8 void g(N1::S s) {
 9   f<3>(s);      // 文法錯誤(無限定查找找不到 f )
10   N1::f<3>(s);  // OK ,有限定查找找到模闆 'f'
11   N2::f<3>(s);  // 錯誤: N2::f 不接收非類型模闆形參
12                 //       N1::f 不能被找到,因為 ADL 僅适用于非限定名
13   using N2::f;
14   f<3>(s); // OK :無限定查找現在找到 N2::f 然後 ADL 表态,
15            //      因為此名無限定并找到 N1::f
16 }      

下列語境發生僅 ADL 的查找(即僅于關聯的命名空間查找):

  • ​​範圍 for​​ 循環查找非成員函數 ​

    ​begin​

    ​ 與 ​

    ​end​

    ​ ,若成員查找失敗
  • 從模闆執行個體化點的​​依賴名查找​​。
  • 結構化綁定聲明為類tuple類型查找非成員函數get(c++17起)

示例

2       struct X;
 3       struct Y;
 4       void f(int);
 5       void g(X);
 6 }
 7  
 8 namespace B {
 9     void f(int i) {
10         f(i);   // 調用 B::f (無限遞歸)
11     }
12     void g(A::X x) {
13         g(x);   // 錯誤:在 B::g (普通查找)與 A::g (參數依賴查找)間歧義
14     }
15     void h(A::Y y) {
16         h(y);   // 調用 B::h (無限遞歸): ADL 檢驗 A 命名空間
17                 // 但找不到 A::h ,故隻用來自通常查找的 B::h
18     }
19 }      

繼續閱讀