《Effective C++ 中文版 第三版》讀書筆記
條款 42:了解 typename 的雙重意義
template 聲明式中,class 和 typename 這兩個(gè)關(guān)鍵字意義完全相同
template<class T> class Widget;
template<typename T> class Widget;
有時(shí)候你一定要用 typename,
可以在 template 中指涉的兩種名稱:
template <typename C>
void print2nd(const C& container)
{
if (container.size() >= 2)
{
C::const_iterator iter(container.begin());
++iter;
int value = *iter;
std::cout << value;
}
}
iter 的類型是 C::const_iterator 實(shí)際上是什么必須取決于 template 參數(shù) C。template 內(nèi)出現(xiàn)的名稱如果相依于某個(gè) template 參數(shù),稱之為從屬名稱(dependent names)。如果從屬名稱在 class 內(nèi)呈嵌套狀,稱之為嵌套從屬名稱(nested dependent name)。C::const_iterator 就是這樣一個(gè)名稱嵌套從屬名稱。
value 類型 int。不依賴任何 template 參數(shù)的名稱。稱為非從屬名稱(non-dependent name)。
嵌套從屬名稱可能導(dǎo)致解析的困難:
template <typename C>
void print2nd(const C& container)
{
C::const_iterator* x;
}
看起來我們好像聲明一個(gè) local 變量,是個(gè)指針,指向一個(gè) C::const_iterator。 但它之所以被那么認(rèn)為,是因?yàn)槲覀?“已經(jīng)知道” C::const_iterator 是個(gè)類型。如果 C::const_iterator 不是個(gè)類型呢?如果 C 有個(gè) static 成員變量碰巧被命名為 const_iterator。過時(shí) x 碰巧是個(gè) global 變量名稱,那樣上述代碼就是一個(gè)相乘動(dòng)作,C::const_iterator 乘以 x。撰寫 C++ 解析器的人必須操心所有可能的輸入。
在我們知道 C 以前,沒有任何辦法可以知道 C::const_iterator 是否為一個(gè)類型。而當(dāng)編譯器開始解析 template print2nd 時(shí),尚未確定 C 是什么東西。
C++ 有個(gè)規(guī)則可以解析此一歧義狀態(tài):如果解析器在 template 中遭遇一個(gè)嵌套從屬名稱,它便假設(shè)這個(gè)名稱不是個(gè)類型,除非你告訴它是。缺省情況下從屬名稱不是類型。此外還有個(gè)例外。
所以上述代碼不是有效的 C++ 代碼。我們必須告訴 C++ 說 C::const_iterator 是個(gè)類型。只要緊鄰它之前放置關(guān)鍵字 typename 即可:
template <typename C> //這個(gè)合法的 C++ 代碼
void print2nd(const C& container)
{
if (container.size() >= 2)
{
typename C::const_iterator iter(container.begin());
++iter;
int value = *iter;
std::cout << value;
}
}
typename 只用來驗(yàn)明嵌套從屬類型名稱;其他名稱不該有它存在。
template <typename C>
void f(const C& container, // 不允許使用 typename
typename C::iterator iter);// 一定要使用 typename
“typename 必須作為嵌套從屬類型名稱的前綴詞” 這一規(guī)則的例外是,typename 不可以出現(xiàn)在 base classes list 內(nèi)的嵌套從屬類型名稱之前,也不可在 member initialization list(成員初始化列表)中作為 base class 修飾符。例如:
template <typename T>
class Derived: public Base<T>::Nested{ // base class list中不允許“typename”
public:
explicit Derived(int x)
:Base<T>::Nested(x)//mem.init.list中不允許“typename”
{
typename Base<T>::Nested temp;//嵌套從屬類型既不在base class list中也不在mem.init.list中,
} // 作為一個(gè)base class修飾符需加上typename
};
讓我們看一個(gè) typename 例子:一個(gè) function template,他接受一個(gè)迭代器,而我們打算為該迭代器指涉的對(duì)象做一份復(fù)件 temp:
template <typename IterT>
void workWithIterator(IterT)
{
typename std::iterator_traits<IterT>::value_type temp(*iter);
}
這是個(gè)標(biāo)準(zhǔn) trait class 的一種運(yùn)用(條款 47),相當(dāng)于說 “類型 IterT 之對(duì)象所指之物的類型”。如果 IterT 是 vector<int>::iterator,temp 的類型就是 int,如果 IterT 是 list<string>::iterator,temp 的類型就是 string。由于 std::iterator_traits<IterT>::value_type 是個(gè)嵌套從屬類型名稱(value_type 被嵌套于 iterator_traits<IterT> 之內(nèi)而 IterT 是個(gè) template 參數(shù)),所以必須在它之前放置 typename。
這么長(zhǎng)你肯定會(huì)想建立一個(gè) typedef。對(duì)于 traits 成員名稱如 value_type,普遍習(xí)慣是設(shè)定 typedef 名稱用以代表某個(gè) traits 成員名稱:
template <typename IterT>
void workWithIterator(IterT)
{
typedef typename std::iterator_traits<IterT>::value_type value_type;
value_type temp(*iter);
}
請(qǐng)記住:
聲明 template 參數(shù)時(shí),前綴關(guān)鍵字 class 和 typename 可互換。
請(qǐng)使用關(guān)鍵字 typename 標(biāo)識(shí)嵌套從屬類型名稱;但不得在 base class list(基類列表)或 member initialization list(成員初值列表)內(nèi)以它作為 base class 修飾符。