依賴於實參的名字查找

維基百科,自由的百科全書

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


語義[編輯]

依賴於實參的名字查找的先決條件[編輯]

如果通常的未限定(unqualified)名字查找所產生的候選集包括下述情形,則不會啟動依賴於實參的名字查找:

  1. 類成員聲明(此種情形僅指普通的類成員函數,不指類成員運算符函數)
  2. 塊作用域內的函數的聲明,不含(using-declaration)
  3. 任何不是函數或者函數模板的聲明(例如函數對象或者另一個變量其名字與被查詢的函數名字衝突)

命名空間與類的相關集合[編輯]

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

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

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

  1. 通過ADL查找到的相關類內的友函數與函數模版是可見的
  2. 所有的名字,如果不是函數或者函數模版,將被忽略(不與變量名字衝突)

例子[編輯]

一個簡單示例:

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
}

更為複雜、精緻的例子:

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)运算符成员函数
}

另一個示例:

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
}

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

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將無法編譯通過:[2]

#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供標準模板庫的算法使用。換句話說,

std::swap(a, b);

的效果可能相同與或不同於

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

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


參考文獻[編輯]

  1. ^ Working Draft, Standard for Programming Language C++ (PDF). JTC1/SC22/WG21. Chapter 3.4.2 - Argument-dependent name lookup - p. 2. 19 October 2005 [13 March 2012]. (原始內容 (PDF)存檔於2005-12-14). 
  2. ^ 2.0 2.1 A Personal Note About Argument-Dependent Lookup. [7 February 2014]. (原始內容存檔於2018-03-17). 
  3. ^ 《C++14語言標準》3.4.2.2: For each argument type T in the function call, there is a set of zero or more associated namespaces and a set of zero or more associated classes to be considered. The sets of namespaces and classes is determined entirely by the types of the function arguments (and the namespace of any template template argument).

外部連結[編輯]