一、函式模板(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、必須保證所有函式都定義在被呼叫之前。