C++箴言:理解typename的兩個含義
template<class T> class Widget; // uses "class" template<typename T> class Widget; // uses "typename" |
然而,C++ 並不總是把 class 和 typename 視爲等同的東西。有時你必須使用 typename。爲了理解這一點,我們不得不討論你會在一個 template(模板)中涉及到的兩種名字。
假設我們有一個函數的模板,它能取得一個 STL-compatible container(STL 兼容容器)中持有的能賦值給 ints 的對象。進一步假設這個函數只是簡單地打印它的第二個元素的值。它是一個用糊塗的方法實現的糊塗的函數,而且就像我下面寫的,它甚至不能編譯,但是請將這些事先放在一邊——有一種方法能發現我的愚蠢:
template<typename C> // print 2nd element in void print2nd(const C& container) // container; { // this is not valid C++! if (container.size() >= 2) { C::const_iterator iter(container.begin()); // get iterator to 1st element ++iter; // move iter to 2nd element int value = *iter; // copy that element to an int std::cout << value; // print the int } } |
print2nd 中的另一個 local variable(局部變量)value 具有 int 類型。int 是一個不依賴於任何 template parameter(模板參數)的名字。這樣的名字以 non-dependent names(非依賴名字)聞名。(我想不通爲什麼他們不稱它爲 independent names(無依賴名字)。如果,像我一樣,你發現術語 "non-dependent" 是一個令人厭惡的東西,你就和我產生了共鳴,但是 "non-dependent" 就是這類名字的術語,所以,像我一樣,轉轉眼睛放棄你的自我主張。)
nested dependent name(嵌套依賴名字)會導致解析困難。例如,假設我們更加愚蠢地以這種方法開始 print2nd:
template<typename C> void print2nd(const C& container) { C::const_iterator * x; ... } |
直到 C 成爲已知之前,沒有任何辦法知道 C::const_iterator 到底是不是一個 type(類型),而當 template(模板)print2nd 被解析的時候,C 還不是已知的。C++ 有一條規則解決這個歧義:如果解析器在一個 template(模板)中遇到一個 nested dependent name(嵌套依賴名字),它假定那個名字不是一個 type(類型),除非你用其它方式告訴它。缺省情況下,nested dependent name(嵌套依賴名字)不是 types(類型)。(對於這條規則有一個例外,我待會兒告訴你。)
記住這個,再看看 print2nd 的開頭:
template<typename C> void print2nd(const C& container) { if (container.size() >= 2) { C::const_iterator iter(container.begin()); // this name is assumed to ... // not be a type |
template<typename C> // this is valid C++ void print2nd(const C& container) { if (container.size() >= 2) { typename C::const_iterator iter(container.begin()); ... } } |
typename 應該僅僅被用於標識 nested dependent type name(嵌套依賴類型名);其它名字不應該用它。例如,這是一個取得一個 container(容器)和這個 container(容器)中的一個 iterator(迭代器)的 function template(函數模板):
template<typename C> // typename allowed (as is "class") void f(const C& container, // typename not allowed typename C::iterator iter); // typename required |
"typename must precede nested dependent type names"(“typename 必須前置於嵌套依賴類型名”)規則的例外是 typename 不必前置於在一個 list of base classes(基類列表)中的或者在一個 member initialization list(成員初始化列表)中作爲一個 base classes identifier(基類標識符)的 nested dependent type name(嵌套依賴類型名)。例如:
template<typename T> class Derived: public Base<T>::Nested { // base class list: typename not public: // allowed explicit Derived(int x) : Base<T>::Nested(x) // base class identifier in mem { // init. list: typename not allowed typename Base<T>::Nested temp; // use of nested dependent type ... // name not in a base class list or } // as a base class identifier in a ... // mem. init. list: typename required }; |
讓我們來看最後一個 typename 的例子,因爲它在你看到的真實代碼中具有代表性。假設我們在寫一個取得一個 iterator(迭代器)的 function template(函數模板),而且我們要做一個 iterator(迭代器)指向的 object(對象)的局部拷貝 temp,我們可以這樣做:
template<typename IterT> void workWithIterator(IterT iter) { typename std::iterator_traits<IterT>::value_type temp(*iter); ... } |
如果你覺得讀 std::iterator_traits<IterT>::value_type 令人討厭,就想象那個與它相同的東西來代表它。如果你像大多數程序員,對多次輸入它感到恐懼,那麼你就需要創建一個 typedef。對於像 value_type 這樣的 traits member names(特性成員名),一個通用的慣例是 typedef name 與 traits member name 相同,所以這樣的一個 local typedef 通常定義成這樣:
template<typename IterT> void workWithIterator(IterT iter) { typedef typename std::iterator_traits<IterT>::value_type value_type; value_type temp(*iter); ... } |
作爲結束語,我應該提及編譯器與編譯器之間對圍繞 typename 的規則的執行情況的不同。一些編譯器接受必需 typename 時它卻缺失的代碼;一些編譯器接受不許 typename 時它卻存在的代碼;還有少數的(通常是老舊的)會拒絕 typename 出現在它必需出現的地方。這就意味着 typename 和 nested dependent type names(嵌套依賴類型名)的交互作用會導致一些輕微的可移植性問題。
Things to Remember
在聲明 template parameters(模板參數)時,class 和 typename 是可互換的。
用 typename 去標識 nested dependent type names(嵌套依賴類型名),在 base class lists(基類列表)中或在一個 member initialization list(成員初始化列表)中作爲一個 base class identifier(基類標識符)時除外。