一些關鍵概念
在我們揭開真實原因的面紗之前,先保持一點神秘感,因為為了更好的理解C++標准,有幾個重要的概念需要先行介紹一下。
限定名和非限定名
限定名(qualified name),故名思義,是限定了命名空間的名稱。看下面這段代碼,cout和endl就是限定名:
#include <iostream> int main() { std::cout << "Hello world!" << std::endl; }
cout和endl前面都有std::,它限定了std這個命名空間,因此稱其為限定名。
如果在上面這段代碼中,前面用using std::cout;或者using namespace std;,然后使用時只用cout和endl,它們的前面不再有空間限定std::,所以此時的cout和endl就叫做非限定名(unqualified name)。
依賴名和非依賴名
依賴名(dependent name)是指依賴於模板參數的名稱,而非依賴名(non-dependent name)則相反,指不依賴於模板參數的名稱。看下面這段代碼:
template <class T> struct MyClass { int i; vector<int> vi; vector<int>::iterator vitr; T t; vector<T> vt; vector<T>::iterator viter; };
因為是內置類型,所以類中前三個定義的類型在聲明這個模板類時就已知。然而對於接下來的三行定義,只有在模板實例化時才能知道它們的類型,因為它們都依賴於模板參數T。因此,T, vector<T>和vector<T>::iterator稱為依賴名。前三個定義叫做非依賴名。
更為復雜一點,如果用了typedef T U; U u;,雖然T沒再出現,但是U仍然是依賴名。由此可見,不管是直接還是間接,只要依賴於模板參數,該名稱就是依賴名。
typename的標記作用:
結束以上兩個個概念的討論,讓我們接着揭開typename的神秘面紗。
一個例子
在Stroustrup起草了最初的模板規范之后,人們更加無憂無慮的使用了class很長一段時間。可是,隨着標准化C++工作的到來,人們發現了模板這樣一種定義:
template <typename T> void foo() { T::iterator iter; // ... }
在上例代碼中T本身已經是模板的類型參數,它只有等到模板實例化時才會知道是哪種類型,更不用說由T定義的內部的iterator
。
編譯器不知道第三行代碼到底是,定義一個變量還是定義一個新類型,這樣同一行代碼能以兩種完全不同的方式解釋,而且在模板實例化之前,完全沒有辦法來區分它們,這絕對是滋生各種bug的溫床。這時C++標准委員會再也忍不住了,與其到實例化時才能知道到底選擇哪種方式來解釋以上代碼,委員會決定引入一個新的關鍵字,這就是typename
。
c++標准定義到:
對於用於模板定義的依賴於模板參數的名稱,只有在實例化的參數中存在這個類型名,或者這個名稱前使用了typename
關鍵字來修飾,編譯器才會將該名稱當成是類型。除了以上這兩種情況,絕不會被當成是類型。
因此,如果你想直接告訴編譯器T::iterator
是類型而不是變量,只需用typename
修飾:
template <typename T> void foo() { typename T::iterator iter; // ... }
通過加上typename關鍵字,這樣編譯器就可以確定T::iterator
是一個類型,而不再需要等到實例化時期才能確定,因此消除了歧義。