C++ Primer 筆記-第16章 模板與泛型編程

16.1 定義模板
16.1.1 函數(shù)模板
我們可以定義一個通用的函數(shù)模板(function template),而不是為每個類型都定義一個新函數(shù)。一個函數(shù)模板就是一個公式,可用來生成針對特定類型的函數(shù)版本。
模板定義以關鍵字
template
開始,后跟一個模板參數(shù)列表(template parameter list),模板參數(shù)列表不能為空。

template <typename T>
int compare(const T& v1, const T& v2){
? ?if(v1 < v2) return -1;
? ?if(v1 > v2) return 1;
? ?return 0;
}

編譯器用推斷出的模板參數(shù)來為我們**實例化(instantiate)**一個特定版本的函數(shù)。使用模板實參代替對應的模板參數(shù)來創(chuàng)建出模板的一個新"實例"。
我們可以將**類型參數(shù)(type parameter)**看作類型說明符,就像內(nèi)置類型或類類型說明符一樣使用。
類型參數(shù)可以用來指定返回類型或函數(shù)的參數(shù)類型,以及在函數(shù)體內(nèi)用于變量聲明或類型轉(zhuǎn)換:

// 類型參數(shù)為 T
template <typename T> T foo(T* p){
? ?T tmp = *p;
? ?// ...
? ?return tmp;
}

除了定義類型參數(shù),還可以在模板中定義非類型參數(shù)(nontype parameter)。一個非類型參數(shù)表示一個值而非一個類型。通過一個特定的類型名而非關鍵字
class
或typename
來指定非類型參數(shù)。當一個模板被實例化時,非類型參數(shù)被一個用戶提供的或編譯器推斷出的值所代替。這些值必須是常量表達式,從而允許編譯器在編譯時實例化模板。
一個非類型參數(shù)可以是一個整型,或者是一個指向?qū)ο蠡蚝瘮?shù)類型的指針或(左值)引用。
綁定到非類型整型參數(shù)的實參必須是一個常量表達式。綁定到指針或引用的非類型參數(shù)的實參必須具有靜態(tài)的生存期。
不能用一個普通(非
static
)局部變量或動態(tài)對象作為指針或引用非類型模板參數(shù)的實參。指針參數(shù)也可以用nullptr
或一個值為0的常量表達式來實例化。

template <unsigned N, unsigned M>
int compare(const char (&p1)[N], const char (&p2)[M]){
? ?return strcmp(p1, p2);
}
// 調(diào)用模板:
compare("hi", "mom");
// 模板將會實例化成:
int compare(const char (&p1)[3], const char (&p2)[4])

模板程序應該盡量減少對實參類型的要求。
函數(shù)模板和類模板成員函數(shù)的定義通常放在頭文件中。
大多數(shù)編譯錯誤在實例化期間報告。
保證傳遞給模板的實參支持模板所要求的操作,以及這些操作在模板中能正確工作,是調(diào)用者的責任。
16.1.2 類模板
**類模板(class template)是用來生成類的藍圖的。
與函數(shù)模板的不同之處是,編譯器不能為類模板推斷模板參數(shù)類型。為了使用類模板,我們必須在模板名后的尖括號中提供額外信息-用來代替模板參數(shù)的模板實參列表。
使用模板時,需要提供顯式模板實參(explicit template argument)列表作為額外信息,它們被綁定到模板參數(shù)。編譯器使用這些模板參數(shù)來實例化特定的類。

template <typename T> class Blob {
public:
? ?Blob();
? ?Blob(std::initializer_list<T> il);
? ?// ... ...
private:
? ?std::shared_ptr<std::vector<T>> data;
? ?// ... ...
}
Blob<int> ia; ? ? ? ? ? ? ? // 空Blob<int>
Blob<int> ia2 = {0, 1, 2} ? // 有3個元素的Blob<int>
// 編譯器會實例化出一個與下面定義等價的類:
template <> class Blob<int> {
public:
? ?Blob();
? ?Blob(std::initializer_list<int> il);
? ?// ... ...
private:
? ?std::shared_ptr<std::vector<int>> data;
? ?// ... ...
}

一個類模板的每個實例都形成一個獨立的類。類型
Blob<string>
與任何其他的Blob
類型都沒有關聯(lián),也不會對其他任何Blob
類型的成員有特殊訪問權(quán)限。默認情況下,對于一個實例化了的類模板,其成員只有在使用時才被實例化。這一特征是的即使某種類型不能完全符合模板操作的要求,我們?nèi)匀荒苡迷擃愋蛯嵗悺?/p>
在類模板自己的作用域內(nèi),可以直接使用模板名而不提供實參,編譯器處理模板自身的引用時就好像我們已經(jīng)提供了與模板參數(shù)匹配的實參一樣。
當我們在類模板外定義其他成員時,必須記住,我們并不在類的作用域中,直到遇到類名才表示進入類的作用域。
當一個類包含一個友元聲明時,類與友元各自是否是模板是相互無關的。
如果一個類模板包含一個非模板友元,則友元被授權(quán)可以訪問所有模板實例。如果友元自身是模板,類可以授權(quán)給所有友元模板實例,也可以只授權(quán)給特定實例。
模板類與另一個(類或函數(shù))模板間友好關系的最常見的形式是建立對應實例及其友元間的友好關系。
一個類也可以將另一個模板的每一個實例都聲明為自己的友元,或者限定特定的實例為友元。
在新標準中,可以令模板自己的類型參數(shù)成為友元:

template <typename Type> class Bar{
? ?friend Type;????????// 將訪問權(quán)限授予用來實例化Bar的類型 ? ?
????// ...
}

類模板的一個實例定義了一個類類型,與任何其他類類型一樣,我們可以定義一個
typedef
來引用實例化的類:

typedef Blob<string> StrBlob;
// 由于模板不是類型,我們無法定義一個typedef引用一個模板:
typedef Blob<T> TBlob;
// 新標準允許我們?yōu)轭惸0宥x一個類型別名:
template <typename T> using twin = pair<T, T>;
twin<string> authors; ? // 一個pair<string, string>
// 可以固定一個或多個模板參數(shù)
template <typename T> using partNo = pair<T, unsigned>;
partNo<string> books; ? // 一個pair<string, unsigned>

類模板可以聲明
static
成員。類似任何其他的成員函數(shù),一個static成員函數(shù)只有在使用時才會實例化。

template <typename T> class Foo {
public:
? ?static std::size_t count() { return ctr; }
private:
? ?static std::size_t ctr;
};
// 將static數(shù)據(jù)成員也定義為模板
template <typename T>
size_t Foo<T>::ctr = 0; // 定義并初始化ctr
// 訪問靜態(tài)成員函數(shù):
Foo<int> fi; ? ? ? ? ? ? ? ? // 實例化Foo<int>類和static數(shù)據(jù)成員ctr
auto ct = Foo<int>::count(); // 實例化Foo<int>::count
ct = fi.count(); ? ? ? ? ? ? // 使用Foo<int>::count
ct = Foo::count(); ? ? ? ? ? // 錯誤:使用哪個模板實例的count?

16.1.3 模板參數(shù)
一個模板參數(shù)名的可用范圍是在其聲明之后,至模板聲明或定義結(jié)束之前。
與其他任何名字一樣,模板參數(shù)會隱藏外層作用域中聲明的相同名字。但是,與大多數(shù)其他上下文不同,在模板內(nèi)不能重用模板參數(shù)名:

typedef double A;
template <typename A, typename B> void f(A a, B b) {
? ?A tmp = a; ?// tmp的類型為模板參數(shù)A的類型,為double
? ?double B; ? // 錯誤:重聲明模板參數(shù)B
}
// 由于參數(shù)名不能重用,所以一個模板參數(shù)名在一個特定模板參數(shù)列表中只能出現(xiàn)一次:
// 錯誤:非法重用模板參數(shù)名V
template <typename V, typename V> // ...

一個特定文件所需要的所有模板的聲明通常一起放置在文件開始位置,出現(xiàn)于任何使用這些模板的代碼之前。
c++語言假定通過作用域運算符訪問的名字不是類型。因此,如果我們希望使用一個模板類型參數(shù)的類型成員,就必須顯式告訴編譯器該名字是一個類型。我們通過關鍵字
typename
來實現(xiàn)這一點。當我們希望通知編譯器一個名字表示類型時,必須使用關鍵字
typename
,而不能使用class
。

template <typename T>
typename T::value_type top(const T& c) {
? ?if(!c.empty()) return c.back();
? ?else return typename T::value_type();
}

可以給函數(shù)的模板參數(shù)提供**默認實參(default template argument)。
與函數(shù)默認實參一樣,對于一個模板參數(shù),只有當它右側(cè)的所有參數(shù)都有默認實參時,它才可以有默認實參。

// compare有一個默認模板實參less<T>和一個默認函數(shù)實參F()
template <typename T, typename F = less<T>>
int compare(const T& v1, const T& v2, F f = F()) {
? ?if(f(v1, v2)) return -1;
? ?if(f(v2, v1)) return 1;
? ?return 0;
}

無論何時使用一個類模板,我們都必須在模板名之后接上尖括號。尖括號指出類必須從一個模板實例化而來。
特別是,如果一個類模板為其所有模板參數(shù)都提供了默認實參,且我們希望使用這些默認實參,就必須在模板名之后跟一個空尖括號對:

template <class T = int> class Numbers { // T默認為int
public:
? ?Numbers(T v = 0) : val { }
? ?// ...
private:
? ?T val;
};
Numbers<long long> a; ? // 使用long long實例化類模板
Numbers<> b; ? ? ? ? ? ?// 空<>表示希望使用默認類型(int)

16.1.4 成員模板
一個類(無論是普通類還是類模板)可以包含本身是模板的成員函數(shù)。這些成員被稱為成員模板(member template)。成員模板不能是虛函數(shù)。
對于類模板,我們也可以為其定義成員模板。在此情況下,類和成員各自有自己的、獨立的模板參數(shù)。

template <typename T> class Blob {
? ?template <typename It> Blob(It b, It e);
? ?// ...
}
// 與類模板的普通成員函數(shù)不同,成員模板是函數(shù)模板。
// 當我們在類模板外定義一個成員模板時,必須同時為類模板和成員模板提供模板參數(shù)列表。
// 類模板的參數(shù)列表在前,后跟成員自己的模板參數(shù)列表:
template <typename T> ? // 類的類型參數(shù)
tempalte <typename It> ?// 構(gòu)造函數(shù)的類型參數(shù)
? ?Blob<T>::Blob(It b, It e): data(std::make_shared<std::vector<T>>(b, e)) {}

16.1.5 控制實例化
在大系統(tǒng)中,在多個文件中實例化相同模板的額外開銷可能非常嚴重。在新標準中,我們可以通過**顯示實例化(explicit instantiation)**來避免這種開銷。
當編譯器遇到?
extern
?模板聲明時,它不會在本文件中生成實例化代碼。將一個實例化代碼聲明為?
exter
?就表示承諾在程序其他位置有該實例化的一個非?extern
?聲明(定義)。對于一個給定的實例化版本,可能有多個?
extern
?聲明,但必須只有一個定義。在一個類模板的實例化定義中,所用類型必須能用于模板的所有成員函數(shù)。

// Application.cc
// 這些模板類型必須在程序其他位置進行實例化
extern template class Blob<string>;
extern template int compare(const int&, const int&);
Blob<string> sa1, sa2; ? ? ? ? ?// 實例化會出現(xiàn)在其他位置
// Blob<int>及其接受initializer_list的構(gòu)造函數(shù)在本文件中實例化
Blob<int> a1 = {0, 1, 2, 3};
Blob<int> a2(a1); ? ? ? ? ? ? ? // 拷貝構(gòu)造函數(shù)在本文件中實例化
int i = compare(a1[0], a2[0]); ?// 實例化出現(xiàn)在其他地方
// templateBuild.cc
// 實例化文件必須為每個在其他文件中聲明為extern的類型
// 和函數(shù)提供一個(非extern)的定義
template int compare(const int&, const int&);
template class Blob<string>; ? ?// 實例化類模板的所有成員


16.2 模板實參推斷
對于函數(shù)模板,編譯器利用調(diào)用中的函數(shù)實參來確定其模板參數(shù)。從函數(shù)實參來確定模板實參的過程被稱為模板實參推斷(template argument deduction)。
16.2.1 類型轉(zhuǎn)換與模板類型參數(shù)
如一個函數(shù)形參的類型使用了模板類型參數(shù),那么它采用特殊的初始化規(guī)則。
只有很有限的幾種類型轉(zhuǎn)換會自動地應用于這些實參。編譯器通常不是對實參進行類型轉(zhuǎn)換,而是生成一個新的模板實例。
將實參傳遞給模板類型的函數(shù)形參時,能夠自動應用的類型轉(zhuǎn)換只有?
const
?轉(zhuǎn)換及數(shù)組或函數(shù)到指針的轉(zhuǎn)換。算術(shù)轉(zhuǎn)換、派生類向基類的轉(zhuǎn)換一級用戶定義的轉(zhuǎn)換,都不能應用于函數(shù)模板。
如果形參是引用,則數(shù)組不能轉(zhuǎn)換為指針。
一個模板類型參數(shù)可以用作多個函數(shù)形參的類型。由于只允許有限的幾個類型轉(zhuǎn)換,因此傳遞給這些形參的實參必須具有相同的類型。如果推斷出的類型不匹配,則調(diào)用就是錯誤的。

// 如果希望允許函數(shù)實參進行正常的類型轉(zhuǎn)換,
// 我們可以將函數(shù)模板定義為兩個類型參數(shù):
template <typename A, typename B>
int flexibleCompare(const A& v1, const B& v2) {
? ?if(v1 < v2) return -1;
? ?if(v2 < v1>) return 1;
? ?return 0;
}
// 可以使用不同類型的實參了:
long lng;
flexibleCompare(long, 1024); ? ?// 正確:int(long, int)

函數(shù)模板可以有用普通類型定義的參數(shù),即,不涉及模板類型參數(shù)的類型。這種函數(shù)實參不進行特殊處理:它們正常轉(zhuǎn)換為對應形參的類型:

template <typename T> ostream& print(ostream& os, const T& obj) {
? ?return os << obj;
}
// 如果函數(shù)參數(shù)類型不是模板參數(shù),則對實參進行正常的類型轉(zhuǎn)換
print(cout, 42); ? // 實例化print(ostream& int)
ofstream f("output");
print(f, 10); ? // 使用print(ostream&, int); 將f轉(zhuǎn)換為ostream&

16.2.2 函數(shù)模板顯式實參
顯式模板實參按由左至由右的順序于對應的模板參數(shù)匹配。只有尾部(最右)參數(shù)的顯式模板實參才可以忽略,而且前提是是它們可以從函數(shù)參數(shù)推斷出來。
對于模板類型參數(shù)以及顯式指定了的函數(shù)實參,可以進行正常的類型轉(zhuǎn)換:

long lng;
compare(lng, 1024); // 錯誤:模板參數(shù)不匹配
compare<long>(lng, 1024); ? // 正確:實例化compare(long, long)
compare<int>(lng, 1024); ? ?// 正確:實例化compare(int, int)

16.2.3 尾置返回類型與類型轉(zhuǎn)換
尾置返回允許我們在參數(shù)列表之后聲明返回類型,它可以使用函數(shù)的參數(shù):
decltype(*beg)
返回元素類型的引用類型。remove_reference::type
脫去引用,剩下元素類型本身。

template <typename It>
auto fcn(It beg, It end) -> decltype(*beg){
? ?// 處理序列
? ?return *beg; ? ?// 返回序列中第一個元素的引用
}
// 當我們不想返回元素的引用,而是想返回元素的值時:
template <typename It>
auto fcn(It beg, It end) ->
? ?typename remove_reference<decltype<*beg>>::type {
? ?// 處理序列
? ?return *beg; ? // 返回序列中第一個元素的拷貝
}
// 注意,`type`是一個類的成員,而該類依賴于一個模板參數(shù)。
// 因此,必須在返回類型的聲明中使用typename來告知編譯器,type表示一個類型。

16.2.4 函數(shù)指針和實參推斷
當一個函數(shù)的參數(shù)是一個函數(shù)模板實例的地址時,程序上下文必須滿足:對每個模板參數(shù),能唯一確定其類型或值。

template <typename T> int compare(const T&, const T&);
// pf1指向?qū)嵗齣nt compare(const int&, const int&)
int (*pf1) (const int& , const int&) = compare;
// func的重載版本,每個版本接受一個不同的函數(shù)指針類型
void func(int(*)(const string& const string&));
void func(int(*)(const int&, const int&));
func(compare);??// 錯誤:不知道使用哪一個compare的實例
// 正確:顯式指出實例化compare(const int&, const int&)版本
func(compare<int>);

16.2.5 模板實參推斷和引用
如果一個函數(shù)參數(shù)是指向模板參數(shù)類型的右值引用(如,T&&),則可以傳遞給它任意類型的實參。
引用折疊只能應用于間接創(chuàng)建的引用的引用,如類型別名或模板參數(shù):
(1) T& &、T& &&、T&& & 都折疊成類型 T&
(2) 類型 T&& && 折疊成 T&&右值引用通常用于兩種情況:模板轉(zhuǎn)發(fā)其實參或模板被重載。
16.2.6 理解 std::move
標準庫是這樣定義
move
的:

template <typename T>
typename remove_reference<T>::type&& move(T&& t) {
? ?return static_cast<typename remove_reference<T>::type&&>(t);
}
string s1("hi!"), s2;
// 傳入右值
s2 = std::move(string("bye!"));
// 這個調(diào)用會實例化為:
string&& move(string&& t);
// 傳入左值
s2 = std::move(s1);
// 這個調(diào)用會實例化為:
string&& move(string& t);

從一個左值
static_cast
到一個右值引用是允許的通常情況下,
static_cast
只能用于其它合法的類型轉(zhuǎn)換。但是這里有一條針對右值引用的特許規(guī)則:雖然不能隱式地將一個左值轉(zhuǎn)換為右值引用,但是我們可以用static_cast
顯式地將一個左值轉(zhuǎn)換為一個右值引用。對于操作右值引用的代碼來說,將一個右值引用綁定到一個左值的特性允許它們截斷左值。有時候我們知道截斷一個左值是安全的。
(1) 一方面,通過允許進行這樣的轉(zhuǎn)換,c++語言認可了這種用法。
(2) 另一方面,通過強制使用?static_cast
,c++語言試圖阻止我們意外地進行這種轉(zhuǎn)換。統(tǒng)一使用
std::move
使得我們在程序中查找潛在的截斷左值的代碼變得很容易。
16.2.7 轉(zhuǎn)發(fā)
某些函數(shù)需要將其一個或多個實參連同類型不變地轉(zhuǎn)發(fā)給其他函數(shù)。在此情況下,我們需要保持被轉(zhuǎn)發(fā)實參的所有性質(zhì),包括實參類型是否是
const
的以及實參是左值還是右值。通過將一個函數(shù)參數(shù)定義為一個指向模板類型參數(shù)的右值引用,我們可以保持其對應實參的所有類型信息。而使用引用參數(shù)(無論是左值還是右值)使得我們可以保持?
const
?屬性,因為在引用類型中的?const
?是底層的。

tempalte <typename F, typename T1, typename T2>
void flip2(F f , T1&& t1, T2&& t2) {
? ?f(t2, t1);
}

當用于一個指向模板參數(shù)類型的右值引用函數(shù)參數(shù)(
T&&
)時,forward
會保持實參類型的所有細節(jié)。與
std::move
相同,對std::forward
不適用using聲明是一個好主意。

template <template F, tempalte T1, template T2>
void flip(F f, T1&& t1, T2&& t2) {
? ?f(std::forward<T2>(t2), std::forward<T1>(t1));
}


16.3 重載與模板
函數(shù)模板可以被另一個函數(shù)模板或一個普通非模板函數(shù)重載。與往常一樣,名字相同的函數(shù)必須具有不同數(shù)量或類型的參數(shù)。
正確定義一組重載的函數(shù)模板需要對類型間的關系及模板函數(shù)允許的有限的實參類型轉(zhuǎn)換有深刻的理解。
當有多個重載模板對一個調(diào)用提供同樣好的匹配時,應該選擇最特例化的版本。
對于一個調(diào)用,如果一個非函數(shù)模板于一個函數(shù)模板提供同樣好的匹配,則選擇非模板版本。
在定義任何函數(shù)之前,記得聲明所有重載的函數(shù)版本。這樣就不必擔心編譯器由于未遇到你希望調(diào)用的函數(shù)而實例化一個并非你所需的版本。

16.4 可變參數(shù)模板
一個可變參數(shù)模板(variadic template),就是一個接受可變數(shù)目參數(shù)的模板函數(shù)和模板類。可變數(shù)目參數(shù)被稱為參數(shù)包(parameter packet)。
存在兩種參數(shù)包:模板參數(shù)包(template parameter packet),表示零個或多個模板參數(shù);函數(shù)參數(shù)包(function parameter packet),表示零個或多個函數(shù)參數(shù)。
在一個模板參數(shù)列表中,
class...
或typename...
指出接下來的參數(shù)表示零個或多個類型的列表。

// Args是一個模板參數(shù)包,rest是一個函數(shù)參數(shù)包
// Args表示零個或多個模板類型參數(shù)
// rest表示零個或多個函數(shù)參數(shù)
template <tempalte T, typename... Args>
void foo(const T& t, const Args& ... rest);

當我們需要知道包中有多少元素時,可以使用
sizeof...
運算符。

template <typename...Args> void g(Args... args) {
? ?cout << sizeof...(Args) << endl; ? ?// 類型參數(shù)的數(shù)目
? ?cout << sizeof...(args) << endl; ? ?// 函數(shù)參數(shù)的數(shù)目
}

16.4.1 編寫可變參數(shù)函數(shù)模板
可變參數(shù)函數(shù)通常是遞歸的。第一步調(diào)用處理包中的第一個實參,然后剩余實參調(diào)用自身。
對于最后一個調(diào)用,兩個函數(shù)提供同樣好的匹配。但是,非可變參數(shù)模板比可變參數(shù)更特例化,因此編譯器選擇非可變參數(shù)版本。
當定義可變參數(shù)版本的
print
時,非可變參數(shù)版本的聲明必須在作用域中。否則,可變參數(shù)版本會無限遞歸。

// 該模板用來終止遞歸并打印最后一個元素
// 此函數(shù)必須在可變參數(shù)版本的print定義之前聲明
template<typename T>
ostream& print(ostream& os, const T& t) {
? ?return os << t; // 包中最后一個元素之后不打印分隔符
}
// 包中除了最后一個元素之外的其他元素都會調(diào)用這個版本的print
template<typename T, typename... Args>
ostream& print(ostream& os, const T& t, const Args&... rest){
? ?os << t << ", "; ? ?// 打印第一個實參
? ?return print(os, rest...); ?// 遞歸調(diào)用,打印其他實參
}

16.4.2 包擴展
對于一個參數(shù)包,除了獲取其大小之外,我們能對它做的唯一的事情就是?擴展(expand)?它。
當擴展一個包時,我們還要提供用于每個擴展元素的?模式(pattern)。
擴展一個包就是將它分解為構(gòu)成的元素,對每個元素應用模式,獲得擴展后的列表。我們通過在模式右邊放一個省略號(...)來觸發(fā)擴展操作。
擴展中的模式會獨立地應用于包中的每個元素

template <typename... Args>
ostream& errorMsg(ostream& os, const Arg&... rest) {
? ?// print(os, debug_rep(a1), debug_rep(a2),...,debug_rep(an))
? ?return print(os, debug_rep(rest)...); ? // 正確
? ?// print(os, debug_rep(a1, a2, ..., an))
? ?return print(os, debug_rep(rest...)); ? // 錯誤:此調(diào)用無匹配函數(shù)
}

16.4.3 轉(zhuǎn)發(fā)參數(shù)包
可變參數(shù)函數(shù)通常將它們的參數(shù)轉(zhuǎn)發(fā)給其他函數(shù)。這種函數(shù)通常具有我們的
emplace_back
函數(shù)一樣的形式:

// fun有零個或多個參數(shù),每個參數(shù)都是一個模板參數(shù)類型的右值引用
template<typename... Args>
void fun(Args&&... args) ? ?// 將Args擴展為一個右值引用的列表
{
? ?// work的實參既擴展Args又擴展args
? ?work(std::forward<Args>(args)...);
}


16.5 模板特例化
當我們不能(或不希望)使用模板版本時,可以定義類或函數(shù)模板的一個特例化版本。
一個特例化版本就是模板的一個獨立的定義,在其中一個或多個模板參數(shù)被指定為特定的類型。
當定義函數(shù)模板的特例化版本時,我們本質(zhì)上接管了編譯器的工作。即,我們?yōu)樵0宓囊粋€特殊實例提供了定義。重要的是要弄清:一個特例化版本本質(zhì)上是一個實例,而非函數(shù)名的一個重載版本。因此特例化不影響函數(shù)匹配。
模板及其特例化版本應該聲明在同一個頭文件中。所有同名模板的聲明應該放在前面,然后是這些模板的特例化版本。

// 打開std命名空間,以便特例化std::hash
namespace std{
template <> // 我們正在定義一個全特例化版本,模板參數(shù)為Sales_data
struct hash<Sales_data>{
? ?// ... ...
? ?size_t operator() (const Sales_data& s) const;
};
size_t
hash<Sales_data>::operator() (const Sales_data& s) const {
? ?return hash<string>()(s.bookNo) ^
? ? ? ? ? hash<unsigned>()(s.units_sold) ^
? ? ? ? ? hash<double>()(s.revenue);
}
}
// 由于hash<Sales_data>使用Sales_data的私有成員,我們必須將它聲明為友元
template<class T> class std::hash; ?// 友元聲明需要的
class Sales_data {
? ?friend class std::hash<Sales_data>;
? ?// 其他成員定義,如前
}
// 為了讓Sales_data的用戶能使用hash的特例化版本,我們應該在
// Sales_data的頭文件中定義該特例化版本。

于函數(shù)模板不同,類模板的特例化不必為所有模板參數(shù)提供實參。可以只指定一部分而非所有模板參數(shù),或是參數(shù)的一部分而非全部特性。
一個類模板的部分特例化(partical specialization)?本身是一個模板,使用它時用戶還必須為那些在特例化版本中未指定的模板參數(shù)提供實參。
只能部分特例化類模板,而不能部分特例化函數(shù)模板。
我們可以只特例化特定成員函數(shù)而不是特例化整個模板。

//只特例化Bar,不特例化整個Foo
template <typename T> struct Foo {
? ?void Bar() {}
? ?// ...
}
template<> ? ? ? ? ? ? ?// 我們正在特例化一個模板
void Foo<int>::Bar() { ?// 我們正在特例化Foo<int>的成員Bar
? ?// 進行應用于int的特例化處理
}
Foo<string> fs; // 實例化Foo<string>::Foo()
fs.Bar(); ? ? ? // 實例化FOO<string>::Bar()
Foo<int> fi; ? ?// 實例化Foo<int>::Foo()
fi.Bar(); ? ? ? // 使用特例化版本的FOO<int>::Bar()

