c++泛型程式設計中函式模板過載和模板特化同時存在時的查詢規則

工程師WWW發表於2013-10-21
支援泛型程式設計是c++非常強大的一個特性,我們可以通過定義一個模板函式和模板類,大大精簡我們的程式碼,極大的增強程式的複用性和魯棒性。之前本人一直對於泛型程式設計這部分了解的比較少,今天重新溫習了一下,所以通過這篇博文來記錄一下自己的一點心得。
首先我們還是以一個例子來開始,我定義了一個如下的函式模板用來實現,兩個物件的比較功能:

點選(此處)摺疊或開啟

  1. template<typename T>
  2. int compare(const T &first, const T &second)
  3. {
  4.     return first > second ? first : second;
  5. }
上述模板非常的簡單,就是用來返回兩個物件中數值較大的物件的副本。在通常的情況下,這個模板工作上是不存在任何問題的,但是如果我們想要比較兩個字串的大小的時候,這個模板的工作,並不能讓我們感到滿意,因為我們在模板的形參中傳遞的是兩個物件的引用,假如我們傳遞兩個字串指標的實參,其實模板中實現的僅僅是對於兩個指標實參指向的地址的比較,而並不是指標所指向的內容的比較,我們如何去解決這樣的一個問題呢,首先我們想到的是可以使用模板的特化,重新定義一個模板的特化版本來處理兩個字串的比較,於是就有了下面的模板特化:

點選(此處)摺疊或開啟

  1. template<> //全特化的表示方法
  2. int compare<const char*>(const char* const &first, const char* const &second)
  3. {
  4.     return (strcmp(first, second) >= 0) ? first : second ;
  5. }
上面的書寫方式,是模板特化時候標準的書寫形式,在本例中我們實現了一個基於字串比較的功能,並且返回了兩者中較大值。但是假如我們子書寫特化版本的時候,忘記了template<>標誌,書寫成如下的形式:

點選(此處)摺疊或開啟

  1.  int compare<const char*>(const char* const &first, const char* const &second)
  2. {
  3.     return (strcmp(first, second) >= 0) ? first : second ;
  4. }
結果會如何呢?這個時候編譯器會告訴我們:

點選(此處)摺疊或開啟

  1. 錯誤: 特例化成員‘::compare<const char*>’需要‘template<>’語法
假如我們忘記了compare後面尖括號的中型別的書寫,或者更有甚者我們把兩者都忘記了會出現什麼情況呢:

點選(此處)摺疊或開啟

  1. int compare(const char* const &first, const char* const &second)  (#add形成了過載)
  2. {
  3.     return strcmp(first, second);
  4. }

  5. template<>
  6. int compare(const char* const &first, const char* const &second)  (#add和compare<const char*>一樣
  7. {
  8.     return strcmp(first, second);
  9. }
這種情況下,編譯器是否還會提示我們嗎?經過編譯發現,當上述兩種情況同時存在的時候編譯器編譯通過。這個是什麼原因呢?
我這個時候實際測試了一下,這兩個函式,為了加以區別,我們修改了一下第一個函式中實現,我們改成下面:

點選(此處)摺疊或開啟

  1. template<> (#add)
  2. int compare<const char*>(const char* const &first, const char* const &second)
  3. {
  4.     return 0 - strcmp(first, second);
  5. }
我們讓第一個函式返回結果的相反數,然後我們來呼叫這個函式,如下:

點選(此處)摺疊或開啟

  1. int main()
  2. {
  3.     int = 10; 
  4.     int b = 2;
  5.     const char *p1 = "dang";
  6.     const char *p2 = "dao";
  7.     cout << "result strcmp: " << compare(p1, p2) << endl;
  8.     cout << "result int: " << compare(a, b) << endl;
  9.     return 0;
  10. }
大家可以想象一下,執行的結果:

點選(此處)摺疊或開啟

  1. result strcmp: 1
  2. result int: 10
這說明了什麼呢,說明在字串比較的時候,呼叫了修改之後的函式實現,而忽略了漏掉了compare後面尖括號中型別定義的特化版本,但是漏掉型別的定義並不是這裡關鍵,即便我們把漏掉的尖括號加上,結果還是一樣的。

原因到底什麼呢?

是這樣的,在c++之中,是允許使用者過載一個函式的,所謂函式的過載,就是我們可以用同一個函式名,通過改變傳入該函式的實參,來定義不同的函式實現。更為強大的一點是,c++也允許我們過載一個函式模板!!

c++中確定函式呼叫的步驟如下:
(1)為這個函式名建立一個候選函式的集合,包括:
a、與被呼叫函式重名的任意普通的函式。
b、任意函式模板的例項化,通過模板實參的推斷匹配
(2)首先是查詢哪些普通函式是可行的(確定這個普通函式是否可行,必須是傳入實參和函式形參型別的精確匹配,經過型別轉換匹配的不符合要求),然後才是查詢模板中的例項來匹配。(#add先普通函式,再模板函式)

所以我們會發現,在本例中我們漏寫完全的函式特化,實際上是對於模板函式的過載,過載函式會優先於模板在函式呼叫時進行匹配,無論是原來的模板還是我們特化之後的模板,他們優先順序都是要低於我們過載的函式,當然過載函式匹配需要形參和傳入實參的完全匹配。

附:

過載函式模板匹配約定***************************

      同名的函式模板、模板顯式特化函式和普通函式的優先選擇順序,總結出以下4點:
  1.如果引數型別以及返回型別完全匹配,則選擇普通函式或者模板顯式特化函式作為呼叫的函式例項。
  2.否則,如果模板函式能夠推匯出一個引數型別以及返回型別完全匹配的函式例項,則選擇函式模板。
  3.否則,如果呼叫函式的實參以及返回型別能夠進行隱式轉換成與普通函式或者模板顯式特化函式的型別匹配,則選擇普通函式或者模板顯式特化函式。
  4.如果以上三條都不能匹配,則函式匹配失敗,發生編譯錯誤。


相關文章