1. 程式人生 > >一、函式模板(Function Template)

一、函式模板(Function Template)

本系列是《C++Template》(作者:David Vandevoorde, Nicolai M. Josuttis)的學習總結。

1:函式模板(Function Template)

所謂函式模板是由引數表示的一系列的函式。函式模板可以被不同的型別引數所呼叫,使用時和普通的函式功能一樣,不同的是函式模板在定義的時候引數的型別是未知的。

1.1.、例子,模板定義

template <typename T> 
inline T const& max (T const& a, T const& b) 
{ 
    // if a < b then use b else use a 
return a<b?b:a; }

以上程式碼的功能是求兩個值中的最大值,引數的型別是不確定的,在此宣告為模板引數T。
模板引數的宣告如下:

template < comma-separated-list-of-parameters > 

上面的例子中的引數是,由一對<>擴起來。typename 是關鍵字,T是型別引數標識,如果有多個引數識別符號用”,”分隔。當然引數識別符號也可以使用其它的識別符號,只是習慣上用T(T是Type的首字母)。

注:有的地方關鍵字typename也會用class替代兩者沒有差別,但是在本系列學習筆記中用typename,關鍵字class只用來表示類。

1.2、模板的使用

#include <iostream> 
#include <string> 
#include "max.hpp" 

int main() 
{ 
    int i = 42; 
    std::cout << "max(7,i): " << ::max(7,i) << std::endl; 

    double f1 = 3.4; 
    double f2 = -6.7; 
    std::cout << "max(f1,f2): " << ::max(f1,f2) << std
::endl; std::string s1 = "mathematics"; std::string s2 = "math"; std::cout << "max(s1,s2): " << ::max(s1,s2) << std::endl; }

結果如下:

max(7,i):   42 
max(f1,f2): 3.4 
max(s1,s2): mathematics

上面的例子中函式模板max()被呼叫了三次,每次呼叫的時候使用的引數型別是int,double和string

注:上面呼叫max()前面都使用了::,這是因為在標準模板庫中也有個std::max()的模板函式,使用::可以確保max()使用的是我們上面定義的max()函式。

上面的過程看起來好像是由一個max()函式處理三個不同型別的引數,但是實際上並不是這樣的。我們上面的函式模板max(),被編譯器根據呼叫時的不同型別產生了三個函式體。

使用int的函式體:

inline int const& max (int const& a, int const& b) 
{ 
    // if a < b then use b else use a 
    return a<b?b:a; 
}

其它兩種型別的函式體:

const double& max (double const&, double const&); 
const std::string& max (std::string const&, std::string const&); 

由具體的型別取代模板引數的工程被稱為例項化。例項化的結果就是產生了執行是呼叫的真正的函式例項。

注:只要函式模板被呼叫就會例項化

試圖使用某個型別例項化一個模板函式時,如果該型別不支援模板中的操作,就會產生編譯錯誤,例如:

std::complex<float> c1, c2;    // doesn't provide operator < 
… 
max(c1,c2);                    // ERROR at compile time 

上面的例子中,由於比較運算子“<”不支援複數,因此發生編譯錯誤。

在我們編譯程式時,實際過程模板會被編譯兩次。
- 首先,對模板程式碼檢查語法(如缺少分號)等語法錯誤。
- 然後,例項化時,即呼叫模板函式時檢查模板函式中的操作是否支援該型別。

2、引數推導(Argument Deduction)

在上面的例子中,當呼叫max()時模板引數是根據呼叫時的引數推匯出來的。如上面傳入的是int型別,C++編譯器就會推匯出T必須是int。自動推導是沒有型別轉換的,因此傳入入的型別必須嚴格匹配。

template <typename T> 
inline T const& max (T const& a, T const& b); 
… 
max(4,7)     // OK: T is int for both arguments 
max(4,4.2)   // ERROR: first T is int, second T is double

對於第二個max()的呼叫,由於第一個引數是int,第二個double。因此編譯時候會產生錯誤。
這種錯誤,有三種方法可以及解決:

1、轉換引數,讓兩個引數嚴格匹配

max(static_cast<double>(4),4.2)    // OK 

2、顯示指定型別T

max<double>(4,4.2)                 // OK 

3、定義函式模板時指定兩個不同型別的引數

template <typename T1,typename T2> 
inline T const& max (T2 const& a, T2 const& b) 

3、模板引數(Template Parameters)

函式模板有兩種型別的引數:

1、模板引數,使用尖括號”<>”宣告在函式模板名之前。

template <typename T>            // T is template parameter 

2、呼叫引數,使用圓括號”()”宣告在函式模板名之後

max (T const& a, T const& b)   // a and b are call parameters 

模板引數的個數可是任意個,但是不能為模板引數指定預設值。

例如,可以為函式模板max()指定兩個不同型別:

template <typename T1, typename T2> 
inline T1 max (T1 const& a, T2 const& b) 
{ 
    return a < b ? b : a; 
} 
… 
max(4,4.2)   // OK, but type of first argument defines return type 

上面的例子看起來很好,但是有問題。首先,返回型別必須被宣告。如果返回型別是其中的一個模板引數型別,另一個引數型別就可能被轉換成返回的型別。另外一個問題,把第二個型別轉換為第一個型別會產生區域性臨時物件,那麼就不能使用引用的方式(by reference)傳回結果。因此上面的例子中,返回的型別是T1 而不是 T const&

由於呼叫引數(call parameters )是由模板引數(template parameters)構造的,所以兩者是相關的。我們把這種概念稱為:函式模板引數推導。有了推導,就可以像呼叫普通函式那樣呼叫函式模板。
如之前的例子,呼叫時為函式模板顯示指定型別

template <typename T> 
inline T const& max (T const& a, T const& b); 
… 
max<double>(4,4.2)    // instantiate T as double 

當模板引數和呼叫引數沒有直接關係,且編譯器也無法推匯出模板引數是,就需要明確的指定模板引數了。如,可以為max()指定第三個模板引數型別作為返回引數型別。

template <typename T1, typename T2, typename RT> 
inline RT max (T1 const& a, T2 const& b);

然而,推導機制並不會對返回型別進行匹配,而且模板引數RT也不在呼叫引數中。因此,編譯器無法推匯出RT,呼叫時就必須顯示指定型別,如下:

template <typename T1, typename T2, typename RT> 
inline RT max (T1 const& a, T2 const& b); 
… 
max<int,double,double>(4,4.2)    // OK, but tedious 

上面的例子,呼叫時要麼不需要指定引數完全由編譯器推導,要麼就要把所有的引數多顯示指定了。
當然,還有一種方法只明確的指定第一個模板引數,其它的引數由編譯器自動推導。
如下:RT要放在第一個引數的位置。

template <typename RT, typename T1, typename T2> 
inline RT max (T1 const& a, T2 const& b); 
… 
max<double>(4,4.2)    // OK: return type is double 

4、函式模板過載(Overloading Function Templates)

和普通的函式一樣,函式模板也是可以過載的。
函式過載:不同的函式的定義可以有相同的函式名,當函式被呼叫的時候由編譯器判斷使用哪個函式。
如下,函式模板過載的例子:

// maximum of two int values 
inline int const& max (int const& a, int const& b) 
{     
    return a<b?b:a; 
} 
// maximum of two values of any type 
template <typename T> 
inline T const& max (T const& a, T const& b) 
{
    return a<b?b:a; 
} 
// maximum of three values of any type 
template <typename T> 
inline T const& max (T const& a, T const& b, T const& c) 
{ 
    return max (max(a,b), c); 
}

int main() 
{ 
    ::max(7, 42, 68);     // calls the template for three arguments 
    ::max(7.0, 42.0);     // calls max<double> (by argument deduction) 
    ::max('a', 'b');      // calls max<char> (by argument deduction) 
    ::max(7, 42);         // calls the nontemplate for two ints 
    ::max<>(7, 42);       // calls max<int> (by argument deduction) 
    ::max<double>(7, 42); // calls max<double> (no argument deduction) 
    ::max('a', 42.7);     // calls the nontemplate for two ints 
} 

上面的例子中,非模板函式可以和同名的函式模板同時存在,也可以和相同型別的函式模板例項同時存在。當所有的條件都相同時,編譯器會優先選擇非模板函式。
因此上面的第四個呼叫的是非模板函式

max(7, 42)      // both int values match the nontemplate function perfectly 

但是,如果模板更匹配就使用模板的例項化函式,如:

max(7.0, 42.0)  // calls the max<double> (by argument deduction) 
max('a', 'b');  // calls the max<char> (by argument deduction) 

呼叫時可以使用空的模板引數列表”<>”,這種形式告訴編譯器必須使用從函式模板的例項,且模板引數由呼叫引數自動推導。

max<>(7, 42)    // calls max<int> (by argument deduction) 

由於模板是不能進行自動型別轉換的,而普通函式是可以自動型別轉換,所有最後一個呼叫的是非模板函式。

max('a', 42.7)      // only the nontemplate function allows different argument types 

一個更有用的例子是,為指標型別和C-style的字串過載max()模板

#include <iostream> 
#include <cstring> 
#include <string> 

// maximum of two values of any type 
template <typename T> 
inline T const& max (T const& a, T const& b) 
{ 
    return a < b ? b : a; 
} 

// maximum of two pointers 
template <typename T> 
inline T* const& max (T* const& a, T* const& b) 
{ 
    return *a < *b ? b : a; 
} 

// maximum of two C-strings 
inline char const* const& max (char const* const& a, 
                               char const* const& b) 
{ 
    return std::strcmp(a,b) < 0 ? b : a; 
} 

int main () 
{ 
    int a=7; 
    int b=42; 
    ::max(a,b);      // max() for two values of type int 

    std::string s="hey"; 
    std::string t="you"; 
    ::max(s,t);      // max() for two values of type std::string 

    int* p1 = &b; 
    int* p2 = &a; 
    ::max(p1,p2);    // max() for two pointers 

    char const* s1 = "David"; 
    char const* s2 = "Nico"; 
    ::max(s1,s2);    // max() for two C-strings 
} 

上面的所有的過載函式多是傳引用(by reference)。一般來講,過載函式之間有這明顯的差異,或者是引數的個數不同,或者是引數的型別明顯不同,如果差異不夠明顯可能會出現一些意想不到的問題。
如下,以傳值(by value)的方式過載一個傳引用(by referenc)的max(),就無法使用三個引數的max()函式來取得C-style字串的最大者:

// maximum of two values of any type (call-by-reference) 
template <typename T> 
inline T const& max (T const& a, T const& b) 
{ 
    return a < b ? b : a; 
} 

// maximum of two C-strings (call-by-value) 
inline char const* max (char const* a, char const* b) 
{ 
    return std::strcmp(a,b) < 0 ? b : a; 
} 

// maximum of three values of any type (call-by-reference) 
template <typename T> 
inline T const& max (T const& a, T const& b, T const& c) 
{ 
    return max (max(a,b), c); // error, if max(a,b) uses call-by-value 
} 

int main () 
{ 
    ::max(7, 42, 68); // OK 

    const char* s1 = "frederic"; 
    const char* s2 = "anica"; 
    const char* s3 = "lucas"; 
    ::max(s1, s2, s3); // ERROR 

} 

上面的例子中,由於C-style字串的max(a,b)過載函式建立了一個新的區域性值,而改值又以引用的方式傳回,就產生了錯誤。

上面的例子是細微的過載引發的非預期的行為。當函式呼叫時,如果不是所有的過載函式都在當前的範圍可見,那麼上述的問題可能發生,也可能不發生。如下,

// maximum of two values of any type 
template <typename T> 
inline T const& max (T const& a, T const& b) 
{ 
    return a<b?b:a; 
} 

// maximum of three values of any type 
template <typename T> 
inline T const& max (T const& a, T const& b, T const& c) 
{ 
    return max (max(a,b), c);  // uses the template version even for ints 
}                              // because the following declaration comes 
                               // too late: 
// maximum of two int values 
inline int const& max (int const& a, int const& b) 
{ 
    return a<b?b:a; 
} 

由於int型別的max()非模板函式定義在了呼叫之後,那麼“return max (max(a,b), c); 呼叫的是函式模板的int例項。

5、總結

1、函式模板可以針對不同的模板引數定義一系列的函式。
2、函式模板根據呼叫引數型別,例項化不同型別的函式。
3、呼叫時可以顯示指定目標引數。
4、函式模板也可以過載。
5、函式模板過載是,不同的過載函式之間最好存在明顯的差異。
6、必須保證所有函式都定義在被呼叫之前。