『轉』C++箴言:理解typename的兩個含義

C++箴言:理解typename的兩個含義

2005-12-05 09:14 作者: fatalerror99 出處: BLOG

 

  問題:在下面的template declarations(模板聲明)中 class typename 有什麼不同?

template<class T> class Widget; // uses "class"
template<typename T> class Widget; // uses "typename"


  答案:沒什麼不同。在聲明一個 template type parameter(模板類型參數)的時候,class typename 意味着完全相同的東西。一些程序員 更喜歡在所有的時間都用 class,因爲它更容易輸入。其他人(包括我本人)更喜歡 typename,因爲它暗示着這個參數不必要是一個 class type(類類型)。少數開發者在任何類型都被允許的時候使用typename,而把 class 保留給僅接受user-defined types(用戶定義類型)的場合。但是從 C++ 的觀點看,class typename 在聲明一個 template parameter(模板參數)時意味着完全相同的東西。

  然而,C++ 並不總是把 class typename 視爲等同的東西。有時你必須使用 typename。爲了理解這一點,我們不得不討論你會在一個 template(模板)中涉及到的兩種名字。

  假設我們有一個函數的模板,它能取得一個 STL-compatible containerSTL 兼容容器)中持有的能賦值給 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
 }
}


   我突出了這個函數中的兩個 local variables(局部變量),iter valueiter 的類型是 C::const_iterator,一個依賴於 template parameter(模板參數)C 的類型。一個 template(模板)中的依賴於一個 template parameter(模板參數)的名字被稱爲 dependentnames(依賴名字)。當一個 dependent names(依賴名字)嵌套在一個 class(類)的內部時,我稱它爲 nested dependent name(嵌套依賴名字)。C::const_iterator 是一個 nested dependent name(嵌套依賴名字)。實際上,它是一個 nested dependent type name(嵌套依賴類型名),也就是說,一個涉及到一個 type(類型)的 nested dependent name(嵌套依賴名字)。

  print2nd 中的另一個 local variable(局部變量)value 具有 int 類型。int 是一個不依賴於任何 template parameter(模板參數)的名字。這樣的名字以non-dependent names(非依賴名字)聞名。(我想不通爲什麼他們不稱它爲 independentnames(無依賴名字)。如果,像我一樣,你發現術語 "non-dependent" 是一個令人厭惡的東西,你就和我產生了共鳴,但是 "non-dependent" 就是這類名字的術語,所以,像我一樣,轉轉眼睛放棄你的自我主張。)

  nested dependent name(嵌套依賴名字)會導致解析困難。例如,假設我們更加愚蠢地以這種方法開始 print2nd

template<typename C>
void print2nd(const C& container)
{
 C::const_iterator * x;
 ...
}


   這看上去好像是我們將 x 聲明爲一個指向 C::const_iterator local variable(局部變量)。但是它看上去如此僅僅是因爲我們知道 C::const_iterator 是一個 type(類型)。但是如果 C::const_iterator 不是一個 type(類型)呢?如果 C 有一個 static data member(靜態數據成員)碰巧就叫做 const_iterator 呢?再如果 x 碰巧是一個 global variable(全局變量)的名字呢?在這種情況下,上面的代碼就不是聲明一個 local variable(局部變量),而是成爲 C::const_iterator乘以 x!當然,這聽起來有些愚蠢,但它是可能的,而編寫 C++解析器的人必須考慮所有可能的輸入,甚至是愚蠢的。

  直到 C 成爲已知之前,沒有任何辦法知道C::const_iterator 到底是不是一個 type(類型),而當 template(模板)print2nd 被解析的時候,C 還不是已知的。C++ 有一條規則解決這個歧義:如果解析器在一個 template(模板)中遇到一個 nested dependent name(嵌套依賴名字),它假定那個名字不是一個 type(類型),除非你用其它方式告訴它。缺省情況下,nested dependentname(嵌套依賴名字)不是 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


   這爲什麼不是合法的 C++ 現在應該很清楚了。iter declaration(聲明)僅僅在 C::const_iterator 是一個 type(類型)時纔有意義,但是我們沒有告訴 C++ 它是,而 C++ 就假定它不是。要想轉變這個形勢,我們必須告訴 C++ C::const_iterator 是一個 type(類型)。我們將 typename 放在緊挨着它的前面來做到這一點:

template<typename C> // this is valid C++
void print2nd(const C& container)
{
if (container.size() >= 2) {
typename C::const_iterator iter(container.begin());
...
}
}


  通用的規則很簡單:在你涉及到一個在 template(模板)中的 nested dependent type name(嵌套依賴類型名)的任何時候,你必須把單詞 typename 放在緊挨着它的前面。(重申一下,我待會兒要描述一個例外。)

   typename 應該僅僅被用於標識 nesteddependent 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


   C 不是一個 nesteddependent type name(嵌套依賴類型名)(它不是嵌套在依賴於一個 templateparameter(模板參數)的什麼東西內部的),所以在聲明 container 時它不必被 typename 前置,但是 C::iterator 是一個 nested dependent type name(嵌套依賴類型名),所以它必需被typename 前置。

   "typename must precede nested dependent typenames"“typename 必須前置於嵌套依賴類型名)規則的例外是 typename 不必前置於在一個 list of base classes(基類列表)中的或者在一個 memberinitialization list(成員初始化列表)中作爲一個 base classesidentifier(基類標識符)的 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嚇倒你。那僅僅是一個 standard traits class(標準特性類)的使用,用 C++ 的說法就是 "the type of thing pointedto by objects of type IterT"被類型爲 IterT 的對象所指向的東西的類型)。這個語句聲明瞭一個與 IterT objects 所指向的東西類型相同的 local variable(局部變量)(temp),而且用 iter 所指向的object(對象)對 temp 進行了初始化。如果 IterT vector<int>::iteratortemp就是 int 類型。如果 IterT list<string>::iteratortemp 就是 string 類型。因爲std::iterator_traits<IterT>::value_type 是一個nested dependent type name(嵌套依賴類型名)(value_type 嵌套在 iterator_traits<IterT> 內部,而且 IterT 是一個 template parameter(模板參數)),我們必須讓它被 typename 前置。

  如果你覺得讀 std::iterator_traits<IterT>::value_type 令人討厭,就想象那個與它相同的東西來代表它。如果你像大多數程序員,對多次輸入它感到恐懼,那麼你就需要創建一個 typedef。對於像 value_type 這樣的 traits member names(特性成員名),一個通用的慣例是 typedefname 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);
 ...
}


   很多程序員最初發現 "typedeftypename" 並列不太和諧,但它是涉及 nested dependent type names(嵌套依賴類型名)規則的一個合理的附帶結果。你會相當快地習慣它。你畢竟有着強大的動機。你輸入 typename std::iterator_traits<IterT>::value_type 需要多少時間?

  作爲結束語,我應該 提及編譯器與編譯器之間對圍繞 typename 的規則的執行情況的不同。一些編譯器接受必需 typename 時它卻缺失的代碼;一些編譯器接受不許 typename 時它卻存在的代碼;還有少數的(通常是老舊的)會拒絕 typename 出現在它必需出現的地方。這就意味着 typename nested dependent type names(嵌套依賴類型名)的交互作用會導致一些輕微的可移植性問題。

  Things to Remember

  ·在聲明 template parameters(模板參數)時,class typename 是可互換的。

   · typename 去標識 nested dependent type names(嵌套依賴類型名),在 baseclass lists(基類列表)中或在一個 member initialization list(成員初始化列表)中作爲一個 base class identifier(基類標識符)時除外。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章