先看下面很簡單的一小段程序。
#include template typename T>struct Base { void fun() { std::cout <>'Base::fun' <>std::endl; }};template typename T>struct Derived : Base{ void gun() { std::cout <>'Derived::gun' <>std::endl; fun(); }};
這段代碼在 GCC 下很意外地編譯不過,原因竟然是找不到 fun 的定義,可是明明就定義在基類中了好嗎!為什么視而不見呢?顯然這和編譯器對名字的查找方式有關(guān),那這里面究竟有什么玄機(jī)呢?上述代碼是寫得不規(guī)范,還是 GCC 竟然存在這樣愚蠢而又莫名其妙的 bug?
對于模板中引用的符號,C++ 的標(biāo)準(zhǔn)有這樣的要求:
如果名字不依賴于模板中的模板參數(shù),則該符號必須定義在當(dāng)前模板可見的上下文內(nèi)。
如果名字是依賴于模板中的模板參數(shù),則該符號是在實例化該模板時,才對該符號進(jìn)行查找。
也就是說,對于前面提到的例子,gun() 函數(shù)中調(diào)用 fun(),由于該 fun() 并不依賴于 Derived 的模板參數(shù)T,因此在編譯器看來該調(diào)用就相當(dāng)于 ::fun(),直接把它當(dāng)成是一個外部的符號去查找,而此時外部又沒有定義該函數(shù),因此就報錯了。要去除這種錯誤,解決的方法很簡單,只要在調(diào)用 fun 的地方,人為地加上該調(diào)用對模板參數(shù)的依賴則可。
template typename T>struct Derived : Base{ void gun() { std::cout <>'Derived::gun' <>std::endl; this->fun();// or Base::fun(); }};
加上 this 之后,fun 就依賴于當(dāng)前 Derived 類,也就間接依賴了模板參數(shù)T,因此名字的查找就會被推遲到該類被實例化時才去基類中查找。
從前面的介紹,我們可以看到編譯器對模板中引用的符號的查找是分為兩個階段的:
符號不依賴于當(dāng)前模板參數(shù),該符號則被當(dāng)作是外部的符號,直接在當(dāng)前模板所在的域中去查找。
符號如依賴于當(dāng)前模板參數(shù),則對該符號的查找被推遲到模板被實例化時。
為什么要這樣區(qū)別對待呢?原因其實很簡單,編譯器在看到模板 Derived 的定義時,還不能確定它的基類最后是怎樣的:Base
template <>struct Baseint> { void fun2() { std::cout <>'Specialized, Base::fun2' <>std::endl; }};
因此編譯器在看到模板類的定義時,還不能判斷它的基類最后會被實例化成怎樣,所以對依賴于模板參數(shù)的符號的查找只能推遲到該模板被實例化時才進(jìn)行,而如果符號不依賴于模板參數(shù),顯然沒有這個限制,因此可以在看到模板的定義時就直接進(jìn)行查找,于是就出現(xiàn)了對不同符號的兩階段查找。
對于前面介紹中提到的符號,我們其實默認(rèn)指的是變量,細(xì)心的讀者可能會想到,在繼承類中引用的符號,還可能會是類型,而由于模板特化的存在,在名字查找的第一階段編譯器也是沒法判斷出該符號最后到底是怎樣的類型,甚至不能知道是不是一個類型。
template typename T>struct Base { typedef char* baseT;};template typename T>struct Derived : Base{ void gun() { Base::baseT p = 'abc'; }};
template <>struct Baseint>{ typedef int baseT;};template <>struct Basefloat>{ int baseT;};
如上例子,Derived
那么,我們要怎樣才能讓編譯器知道其實 Base
template typename T>struct Derived : Base{ void gun() { typename Base::baseT p = 'abc'; }};
此時,編譯器看到有 typename 顯式地指明 baseT 是一個類型,它就不會再把它默認(rèn)當(dāng)成是一個變量了,從而使得名字查找的第一個階段可以繼續(xù)下去。
模板中名字的查找會因為該名字是否依賴于模板參數(shù)而有所不同。
依賴于模板參數(shù)的名字(如函數(shù)的參數(shù)的類型是模板的參數(shù)),其符號解析會在第二階段進(jìn)行,其查找方式有兩個:
而不依賴于模板參數(shù)的符號,則只會在定義模板的可見域內(nèi)進(jìn)行查找,語言的定義嚴(yán)格如上所述,但實際編譯器的支持上,msvc 不支持兩階段的查找(vc 2010 以前),gcc 的實現(xiàn)在 4.7 以前也不完全符合標(biāo)準(zhǔn),一個比較全面的符合規(guī)范的例子,請參看如下:
void f(char); // 第一個 f 函數(shù) templateclass T> void g(T t) { f(1); // 不依賴參數(shù)的符號,符號解釋在第一階段進(jìn)行,找到 ::f(char) f(T(1)); // 依賴參數(shù)的符號: 查找推遲 f(t); // 依賴參數(shù)的符號: 查找推遲} enum E { e };void f(E); // 第二個 f 函數(shù)void f(int); // 第三個 f 函數(shù) void h() { g(32); // 實例化 g, 此時進(jìn)行查找 f(T(1)) 和 f(t) // f(t) 的查找找到 f(char),因為是通過非 ADL 方式查找的(T 是 int,ADL 失效),而定義模板的域內(nèi)只有 f(char)。 // 同理,f(T(1)) 的查找也只找到 f(char)。 g(e); // 實例化 g, 此時進(jìn)行查找 f(T(1)) 和 f(t),因為參數(shù)都是用戶定義的類型,ADL 起效,因此兩者均找到了 f(E), } typedef double A;templateclass T> class B { typedef int A;};templateclass T> struct X : B { A a; // 此處 A 為 double};
http://gcc.gnu.org/onlinedocs/gcc/Name-lookup.html
http://womble.decadent.org.uk/c++/template-faq.html
http://en.cppreference.com/w/cpp/language/unqualified_lookup
https://gcc.gnu.org/gcc-4.7/porting_to.html
http://en.cppreference.com/w/cpp/language/adl
聯(lián)系客服