參數依賴查找(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() 的函數調用,故不應用 ADL12 13 endl(std::cout); // OK :這是函數調用: ADL 檢驗 std 命名空間, 14 // 因為 endl 的參數在 std ,並找到 std::endl 15 16 (endl)(std::cout); // 錯誤: ‘endl‘ 不聲明於此命名空間。 17 // 子表達式 (endl) 不是函數調用表達式 18 }
細節
首先,若通常非限定查找所生成的集合含有下列任何內容,則不考慮參數依賴查找:
1) 類成員聲明 2) 塊作用域的函數聲明(之非 using 聲明者)否則,對於每個函數調用表達式中的參數,檢驗其類型,以確定它將添加到查找的命名空間與類的關聯集。
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 }
參數依賴查找(ADL,Argument-dependent lookup)