名稱空間
變體
操作

實參依賴查詢

來自 cppreference.com
< cpp‎ | 語言
 
 
C++ 語言
 
 

實參依賴查詢(ADL),也稱為 Koenig 查詢[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 起)

在確定關聯的類和名稱空間集之後,為了進一步的 ADL 處理,該集合中的類中找到的所有宣告都被丟棄,除了下面第 2 點所述的名稱空間作用域友元函式和函式模板。

普通不限定查詢找到的宣告集與 ADL 產生的關聯集所有元素中找到的宣告集合並,並遵循以下特殊規則:

1) 關聯名稱空間中的using 指示被忽略。
2) 在關聯類中宣告的名稱空間作用域友元函式(和函式模板)透過 ADL 可見,即使它們透過普通查詢不可見。
3) 除了函式和函式模板之外的所有名稱都被忽略(不會與變數發生衝突)。

[編輯] 注意

由於實參依賴查詢,與類在同一名稱空間中定義的非成員函式和非成員運算子被視為該類的公共介面的一部分(如果透過 ADL 找到它們)[2]

ADL 是在泛型程式碼中交換兩個物件的既定慣用語背後的原因:using std::swap; swap(obj1, obj2);因為直接呼叫std::swap(obj1, obj2)不會考慮可能在obj1obj2型別所在名稱空間中定義的使用者定義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 查詢(即,僅在關聯名稱空間中查詢):

  • range-for 迴圈在成員查詢失敗時執行的非成員函式beginend的查詢。
(C++11 起)
(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++ 標準。

缺陷報告 應用於 釋出時的行為 正確的行為
CWG 33 C++98 關聯的名稱空間或類未指定
如果用於查詢的實參是
一組過載函式或函式模板的地址
已指定
CWG 90 C++98 巢狀非聯合類的關聯類
不包括其封閉類,但巢狀
聯合與其封閉類相關聯
非聯合也關聯
CWG 239 C++98 在普通不限定查詢中找到的塊作用域函式宣告
並沒有阻止 ADL 發生
using 宣告外,
不考慮 ADL
CWG 997 C++98 在確定函式模板的關聯類和名稱空間時,
依賴引數型別和返回型別被排除在考慮範圍之外
已包含
已包含
CWG 1690 C++98
C++11
ADL 無法找到返回的 lambda (C++11)
或本地類型別物件 (C++98)
可以找到
CWG 1691 C++11 ADL 對於不透明列舉宣告有令人驚訝的行為 已修復
CWG 1692 C++98 雙重巢狀類沒有關聯的名稱空間
(它們的封閉類不是任何名稱空間的成員)
關聯名稱空間
擴充套件到最內層
封閉名稱空間
CWG 2857 C++98 不完整類型別的關聯類
包括其基類
不包括

[編輯] 另請參閱

[編輯] 外部連結

  1. Andrew Koenig:《關於實參依賴查詢的個人說明
  2. H. Sutter (1998) 《類中有什麼?——介面原則》載於 C++ Report, 10(3)