1. 程式人生 > >C++11(15): 模板與泛型程式設計

C++11(15): 模板與泛型程式設計

面向物件程式設計和泛型程式設計都能處理在編寫程式時不知道型別的情況。不同之處:OOP能處理型別在程式執行之前都未知的情況;而泛型程式設計中,在編譯時就能獲知型別了 模板引數類別不能為空。 模板引數表示在類或函式定義中用到的型別或值。 template <typename T> int compare(const T &v1 , const T &v2) {     if(v1<v2) return -1;     if(v2<v1) return 1;     return 0 ; } cout<<compare(1,0) <<endl;//T為int 編譯器生成的版本通常被稱為模板的例項。 特別是,型別引數可以用來指導返回型別或函式引數型別,以及在函式內用於變數宣告或型別轉換: 型別引數前必須加上class或typename(typename是在模板已經廣泛使用之後才引入c++語言的) 非型別行引數必須是常量表達式:template<unsigned N, unsigned M>  當一個模板例項化時,非型別引數被一個使用者提供的或編譯器推匯出來的值所代替。 一個非型別引數可以是一個整型,或者是一個指向物件或函式型別的指標或(左值)引用。繫結到非型別引數的實參時一個常量表達式。繫結到指標或引用非型別引數的實參必須具有靜態的生存期。指標引數也可以用nullptr或一個值為0的常量表達式來例項化。 函式模板可以宣告成inline或constexptr template<typename T> inline T min(const T&, const T&); compare函式說明了編寫泛型程式碼的兩個重要原則:     模板中的函式引數是const的引用。     函式體中的條件判斷僅使用<比較運算 通過將函式引數這位const引用,我們保證函式可以用於不能拷貝的型別。而且,處理大物件,這種設計策略還能使函式執行得更快。 只使用小於,降低了對處理物件的型別要求,不用支援> 是實際上,如果真的關係型別無關和可移植性,可能需要用less來定義,(不用less,存在的問題是,如果使用者呼叫它比較兩個指標,二期兩個指標為指向相同的陣列,則程式碼的行為未定義。 template<typename T> int compare(const T &v1, const T &v2) {     if(less<T>()(v1,v2)) return -1;     if(less<T>()(v2,v1)) return 1;     return 0;
} 模板程式應該儘量減少對實參型別的要求。 當編譯器遇到一個模版定義是,它並不生成程式碼。只有當我們例項化出模板的一個特定版本時,編譯器才會生成程式碼。當我們使用(而不是定義)模板是,編譯器才生成程式碼。 通常,當我們呼叫一個函式時,編譯器只需要掌握函式的宣告。類似的,當我們使用類物件時,類定義必須是可用的,但是成員函式的定義不必已經出現。因此,我們將類定義和函式宣告放在標頭檔案中,而將普通函式和類的成員函式的定義放在原始檔中。 但是模板不同:為了生成一個例項化版本,編譯器需要掌握函式模板或類模板成原函式的定義。因此,模板的標頭檔案通常即包含宣告也包含定義。 當使用模板是,所有不依賴模板引數的名字都必須是可見的,這是有模板的提供者來保證的。而且,模板的提供者必須保證,當模板被例項化時,模板的定義包括類模板的成員定義都是可見的。 用來例項化模板的所有函式、型別以及與型別關聯的運算子的宣告都必須是可見的,這是由模板的使用者來保證的。。 模板知道例項化是才會生成程式碼,這一特性影響了我們何時才會獲知模板內程式碼的編譯錯誤。通常編譯器在三個階段報告錯誤。     第一階段是編譯模板本身時。在這個階段,編譯器通常不會發現很多錯誤。編譯器可以檢查語法錯誤。     第二階段是編譯器遇到模板使用時。在此階段,編譯器仍然沒有很多課檢查的。對於函式模板呼叫,編譯器通常會檢查實引數目是否正確。它還檢查引數型別是否匹配。對於類模板,編譯器可以檢查使用者是否提供了正確數目的模板實參。     第三個階段是模板例項化時,只有在這個階段才能發現型別相關的錯誤。依賴於編譯器如何管理例項化,這類錯誤可能在連結是才報告。 編譯器不能為類模板推斷模板引數型別。必須使用尖括號中提供的額外資訊。 template <typename T>class Blob( public:     typedef T value_type;     typedef typename std::vector<T>::size_tyep size_type;     Blob();     Blob(std::initializer_list<T> il);     size_type size( ) cosnt {return data->size( );}     bool empty( ) const { return data->empty( );}      void push_back(const T &t) { data->push_back(t);}     void push_back(T &&t) {data->push_back(std::move(t)); }     void pop_back();     T& back();     T& operator [](size_type i);   private:     std::shared_ptr<std::vector<T>> data;     void check(size_type i, const std::string &msg) const; } ; 一個類模板的每個例項都是一個相互獨立的類。 應該記住類模板的名字不是一個型別名,而是用來例項化型別,而一個例項化的型別總是包含模板引數的。  一個模板中的程式碼若果使用了另一個模板,我們通常將模板自己的引數作為被使用模板的實參。 std::shared_ptr<std::vector<T>> data; 類模板的成員函式本身是一個普通函式。但是,類模板的每個例項都有其自己版本的成員函式。因此,類模板的成員函式具有和模板相同的模板引數。因而,定義在類模板之外的成員函式就必須以關鍵字template開始,後接類模板引數列表。 template<typename T> void Blob<T>::check(size_type i, const std::string &msg)const {     if (i >= data->size( ))         throw std::out_of_range(mag); } template <typename T> T& Blob<T>::back( ) {     check(0 , "back on empty Blob");     return data->back( ); } template<typename T> T& Blob<T>:: operator[ ](size_type i) {     check(i , "subscript out of range");     return (*data)[i]; } template <typename T> Blob<T>::Blob( ) : data(std::make_shared<std::vector<T>>( ) ) { } template <typename T> Blob<T>::Blob(std::initializer_list<T> il) : data(std::make_shared<std::vector<T>> (il)) { } 在預設情況下,對於一個例項化了的類模板,其成員只有在使用時才被例項化。 當我們使用一個類模板型別時必須提供模板實參,但這一規則有一個例外。在類模板自己的作用域中,我們可以直接使用模板名而不提供實參,當我們處於一個類模板的作用域中時,編譯器處理模板自身引用時就好像我們已經提供了與模板引數匹配的實參一樣。 template <typename T>class BlobPtr{ public:     BlobPtr() : curr(0) { }     BlobPtr(Blob<T> &a, size_t sz = 0) : wptr(a.data), curr(sz) { }     T& operator*( ) const     { auto p = check(curr, "dereference past end");         return (*p)[curr];     }     //注意這裡沒有模板引數     BlobPtr& operator++( );     BlobPtr& operator--(); private:     std::shared_ptr<std::vector<T>> check (std::size_t ,cosnt std::string&) const;     std::weak_ptr<std::vector<T>> wptr;     std::size_t curr; }; 當我們在類模板外定義其成員是,必須記住,我們並不在類的作用域中,知道遇到類名才表示進入類的作用域: template<typename T> BlobPtr<T>& BlobPtr<T>::operator++(int) {     BlobPtr ret = *this ;     ++ *this;     return ret; } 當一個類包含一個友元宣告時,類與友元各自是否是模板是相互無關的。如果一個類模板包含一個非模板友元,則友元被授權可以訪問所有模板例項。如果友元是模板,類可以授權給所有友元模板例項,也可以只授權給特定例項。 類模板與另一個模板友好關係最常見的形式是建立對應例項及其友元間的友好關係。 為了引用模板的一個特定例項,我們必須首先宣告模板自身。 template <typename> class BlobPtr; template <typename> class Blob;//==中的引數所需的。 template <typename T>     bool operator == (const Blob<T>& ,const Blob<T>&) ; template<typename T> class Blob{ //每個Blob例項將訪問許可權授予用相同型別例項化的BlobPtr和相等運算子 friend class BlobPtr<T>; friend bool operator == <T> (const Blob<T>& , const Blob<T>&); }; 如果想讓所有例項都成為友元,友元宣告必須使用與類模板本身不同的模板引數。 //前置宣告,在將模板的一個特定例項宣告為友元是用到。 template<typename T> class Pal; class C{//C非模板     friend class Pal<C>;//用C例項化的Pal是C的一個友元     tempalte <typename T> friend class Pal2;  //Pal2的所有例項都是C的友元;這種情況無須前置宣告 }; template <typename> class C2{     //C2的每個例項將相同例項化的Pal宣告為友元       friend class Pal<T>;  //Pal的模板啥呢麼必須在作用域內     //Pal2的所有例項都是C2的每個例項的友元,不需要前置宣告     template <typename X> friend class Pal2;     //Pal3是一個非模板類,它是C2所有例項的友元     friend class Pal3; //不需要Pal3 的前置宣告。 }; 為了讓所有例項成為友元,友元宣告中必須使用與類模板本身不同的引數。 在新標準中,我們可以將模板引數型別宣告為友元: template <typename  Type> class Bar{ friend Type;  // 將訪問許可權授予用來例項化Bar的型別。 } 值得注意的是,通常友元應該是一個類或一個函式,此時我們也允許用內建型別,一邊我們能用內建型別來例項化這樣的類。 我們可以用模板例項定義typedef,但是不可以用模板。 但是在新標準,我們可以用用using類定義一個模板的別名; template <typename T> using twin = pair<T , T>; template<typename T>using partNo = pair<T, unsigned>; 這個程式碼中我們將partNo定義為一族型別的別名,這族型別是second成員為unsigned的pair 對於static成員來說,每個模板的例項中的static都是相互獨立的,定義是也必須使用模板,訪問也是 Template <typename T> size_t Foo<T>::ctr = 0;//定義並初始化。 auto ct = Foo<int> :: count(); 類似任何其他成員函式,一個static成員函式只有在被使用時才會例項化。 模板引數和普通的引數一樣。 模板宣告必須包含引數 與函式引數相同,宣告中的模板引數的名字不必與定義中相同。 我們用作用域類訪問static成員或型別成員。在普通程式碼中,編譯器掌握類的定義。因此它知道通過作用域訪問符訪問的名字是型別還是static成員。但是對於模板程式碼就存在困難: T::size_type *p; 他需要知道我們是定義一個名為p的變數還是將一個名為size_type的static資料成員與名為p的變數相乘; 預設情況,c++語言假定通過作用域訪問符的名字不是型別。因此,如果我們希望使用一個模板引數的資料成員,就必須顯示的告訴編譯器該名字是一個型別。我們通過是用typename實現: template <typename T> typename T::value_type top(const T& c) {     if(!c.empty() )         return c.back()     else         return typename T::value_type( ); }; 當我希望通知編譯器以個名字表示型別是,必須使用typename 不能用class 新標準中,我們可以為函式和類模板提供預設實參。而更早的版本只允許為類模板提供預設實參。 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; } 與函式預設實參一樣,對於一個模板引數只有當它右側的所有引數都有預設實參時,它才可以有預設實參。 如果我們為所有的模板引數都提供了預設實參,我們在使用模板的時候,同樣應該在模板名後加一對尖括號。 成員模板不能使虛擬函式。 普通類的成員模板: class DebugDelete{ public:     DebugDelete(std::ostream &s = std::cerr) : os(s) { }     template <typename T> void operator() (T *p) const     { os << "deleting unique_ptr"<<std::end;  deletep; } private:     std::ostream &os; }; unique_ptr<int , DebugDelete>p(new int , DebugDelete( )); 對於類模板,我們也可以為其定義成員模板。在此情況下,類和成員各自有自己的、獨立的模板引數。 template <typename T> class Blob{     template <typename It> Blob (It b, Ite); }; 當我們在類外定義,必須同時為類模板和成員模板提供各自的引數,注意順序類模板在前 template<typename T> template<typename It>     Blob<T>::Blob(It b, It e) : data(std::make_shared<std::vector<T>>(b,e)){ } 為了例項或一個類模板,我們必須同時提供類和函式模板的實參。我們在哪個物件上吊用成員模板,編譯器就根據物件的型別類推斷類模板引數的實參。編譯器通常根據傳遞給成員模板的函式實參來推斷它的模板實參。 Blob<int> a1(begin(ia) , end(ia));  //通過ia的型別類推斷成員模板的引數型別。 當兩個或多個獨立編譯的原始檔使用了相同的模板,並提供了相同的模板引數時,每個檔案中都會有該模板的一個例項。這樣可能開銷很大 在新標準中,我們可以通過顯式例項化類避免這種開銷。 //例項化宣告與定義 extern template class Blob<string>; //宣告 template int compare(const int& , const int&) ;  //定義 當編譯器遇到extern模板宣告時,它不會在檔案中生成例項化程式碼。將一個例項化宣告為extern就表示承諾在程式其他位置有該例項化的以個非extern宣告(定義),對於一個給定的例項化版本,可能有多個extern宣告,但必須只有一個定義。 一個類的例項化會定義例項化該模板的所有成員。與普通的例項化不同。因此我們來顯示例項化一個類模板的型別,必須能用於模板的所有成員。 對於shared_ptr和unique_ptr,前者給予我們共享指標所有權的能力,而後者則獨佔。 另一個差異是它們允許使用者過載預設刪除器的方式不同。對於shared_ptr我們只要在建立或Reset指標時傳遞給它以個可到用物件即可。與之相反,刪除器的型別是一個unique_ptr的一部分。使用者必須在定義unique_ptr是以顯式模板實參的形式提供刪除器的型別。對於後者來說,提供自己的刪除去更為複雜。 推斷出,在標準庫中,shared_ptr必須能直接訪問其刪除器。即,刪除器儲存為一個指標或一個封裝了指標的類。 我們可以確定shared_ptr不是將刪除器直接儲存為一個成員,因為刪除器的型別直到執行時才會知道。實際上,在一個shared_ptr的生存期中,我們可以隨時改變其刪除器的型別。我們可以使用一種型別的刪除器構造一個shared_ptr,隨後使用reset賦予此shared_ptr另一種型別的刪除器。通常,類成員的型別在執行時是不能改變的。因此不能直接儲存刪除器。 是在執行時繫結shared_ptr的刪除器的。 unique_ptr可能的工作方式。在這個類中刪除器是類型別的一部分。模板引數的第二個表示刪除器型別。因此刪除器是在編譯時就知道的,從而刪除器可以直接儲存在unique_ptr物件中。 通過在編譯時繫結刪除器,unique_ptr避免了間接呼叫刪除器的執行時開銷。通過在執行時繫結刪除器,shared_ptr使使用者過載刪除器更為方便。 從函式實參類確定模板實參的過程被稱為模板實參推斷。 如果以個函式形參的型別使用了模板型別實參,那麼它採用特殊的初始化原則。只有很有限的集中型別轉換會自動地應用於這些實參。編譯器通常不是對實參進行型別轉換,而是生成一個新的模板例項。 與往常以樣,頂層const無論是在形參中還是在實參中,都會被忽略。在其他轉換中,能在呼叫中應用於函式模板的包括如下兩項:     const轉換:可以將一個非const物件的引用(或指標)傳遞給一個const的引用     陣列或函式指標的轉換:如果函式形參不是引用型別,則可以對陣列或函式型別的實參應用正常的指標轉換。一個數組實參可以轉換為一個指向其受元素的指標。類似的,一個函式實參可以轉換為以個該函式型別的指標。 其他型別的轉換都不能應用於函式模板。 如果函式引數型別不是模板引數,則對實參進行正常的型別轉換。 template <typename T1, typename T2, typename T3> T1 sum(T2, T3); 在本例中,沒有任何函式實參的型別可以用來推斷T1的型別。每次呼叫sum是呼叫者必須為T1提供一個顯示模板實參。 auto val3 = sum<long long>(i, lng);  //long long sum(int, long) 顯示模板實參按由左至右的順序匹配,而且只有尾部引數的顯示模板實參才可以或略,而且前提是它們可以從函式引數推斷出來。 對於普通函式,允許正常的型別轉換,同樣,當模板型別的引數已經顯示指定了函式引數,就相當於已經初始化了模板引數,也進行正常的類想轉換。: long lng; compare(lng,1024);//模板引數不匹配 compare<long>(lng, 1024);//例項化compare(long, long); 顯示指定模板引數會增加使用者的負擔。除此之外我們可以使用後置返回型別,使用decltype來獲取引數類別中表達式的型別。 template<typename It> auto fcn(It beg, It end) -> decltype(*beg) {     return *beg; }  所有的迭代器操作都不會生成元素,只能生成元素的引用。對於我們來說如果我們要返回迭代器中的元素型別,是無法知道的。 為了獲得元素型別,我們可以使用標準庫的型別轉換模板。這些模板在標頭檔案type_traits中。這個標頭檔案中的類通常用於模板超程式設計。 remove_reference<decltype(*beg)>::type   可以脫去引用。 auto fcn2<It beg, Itend) -> typename remove_reference<decltype(*beg)> :: type {     return *beg; } 注意,type是一個類的成員,而該類依賴於一個模板引數,因此,我們必須在返回型別的宣告中使用typename 類告訴編譯器,type表示一個型別。
對Mod<T>,其中Mod為   若T為 則Mod<T>::type為
remove_reference X&或X&&
否則
X
T
add_const X&、const X或函式 否則 T const T
add_lvalue_reference x& x&& 否則 T X& T&
add_rvalue_reference X&或X&&
否則
T
T&&
remove_pointer x* 否則 X
T
add_pointer X& 或X&&
否則
X* T*
make_signed unsigned  X 否則 X
T
make_unsigned 帶符號型別
否則
unsigned X T
remove_extent X[n] 否則 X T
remove_all_extents X[n1][n2]... 否則 X T

當我們用一個函式模板初始化一個函式指標或為函式指標賦值時,編輯器使用指標的型別類推斷模板實參。 當引數是一個函式模板例項的地址時,程式上下文必須滿足:對每個模板引數,能唯一確定其型別或值 在函式型別推斷是,非常重要的兩點:編譯器會應用正常的引用繫結規則;const是底層的,不是頂層的。 當一個函式引數是模板型別引數的一個普通(左值)引用時,繫結規則告訴我們,只能傳遞給它一個左值。如果實參時const的,則T將被推斷為const型別; 如果一個引數是const T&,正常的繫結規則告訴我們可以傳遞給它任何型別的實參:一個物件(const或非const),一個臨時物件或是一個字面常量值。當函式引數本身是const時,T的型別推斷的結果不會是一個const型別。const已經是函式引數型別的一部分;因此,它不會也是模板引數的一部分。 從右值引用推斷和左值類似。 我們通常不能將一個右值引用繫結到一個左值上。但是有兩個例外。這是move能正確工作的基礎。 第一個列外規則影響右值引用引數的推斷如何進行。當我們將一個左值傳遞給函式的右值引用引數,且此右值引用指向模板型別引數時,編譯器推斷模板型別引數為實參的左值引用型別。 通常我們不能(直接)定義一個引用的引用。但是,通過類型別名或通過模板型別引數間接定義是可以的。 在這種情況下,我們可以使用第二個例外繫結規則:如果我們間接建立一個引用的引用,則這些引用形成了“摺疊”。在所有情況下(除了第一個例外),引用會摺疊成一個普通的左值引用型別。在新標準中,摺疊規則擴充套件到右值引用。只在一種特殊情況下引用會摺疊成右值引用:右值引用的右值引用。即,對於一個給定型別X:     X& &、X& &&和X&& & 都摺疊成型別X &     型別X&& &&摺疊成X&& 引用摺疊只能引用於間接建立的引用的引用,如類型別名或模板引數。 這兩個規則導致了兩個重要結果:     如果一個函式引數是一個指向模板型別引數的右值引用(如,T&&),則它可以被繫結到一個左值上;且     如果實參時一個左值,則推斷出阿狸的模板實參型別將是一個左值引用,且函式引數將被例項化為一個(普通)左值引用引數(T&) 如果一個函式引數是指向模板引數型別的右值引用,則可以傳遞給它任意型別的實參。如果將一個左值傳遞給這樣的引數,則函式引數被例項化為一個普通的左值引用。 當代碼中涉及的型別可能是普通(非引用)型別,也可能使引用型別時,編寫正確的程式碼就變得異常困難(雖然remove_reference這樣的型別轉換類可能會有幫助) 在實際中,右值引用通常用於兩種情況:模板轉發其引數或模板被過載。 template <typename T> void f(T&&); //繫結到非const右值 template<typename T> void f(const T&);  //左值和const右值 與非模板函式一樣,第一版本將繫結到可修改的右值,而第二個版本將繫結到左值或const右值。 雖然我們不能直接將一個右值引用banging到一個左值上,但可以用move獲得一個繫結到左值上的右值引用。它本質上接受任何型別的實參,因此我們不會驚訝於它是一個函式模板。 標準庫是這樣定義move的: template<typename T> typename remove_reference<T>::type&& move(T&& t) {     return static_cast<typename remove_reference<T>::type&&>(t); } 通常,static_cast只能作用於其他合法的型別轉換。但是,這裡又有一條針對引用的特許規則:雖然不能隱式地將一個左值轉換為右值,但是可以用static_cast顯示地將一個左值轉換為右值引用。 對於右值引用繫結到一個左值的特許允許它們階段左值。我們知道截斷一個左值是安全的。一方面,通過允許進行這樣的轉換,c++語言認可了這種用法。但令一方面,通過強制使用static_cast,c++語言試圖阻止我們以為的進行這種轉換。 雖然,我們可以直接編寫這樣的轉換程式碼,但是標準庫move函式是容易的多的方式。而且,統一使用std::move是的我們在程式中查詢潛在的截斷左值的程式碼變得容易。 通過將一個函式引數定義為一個指向模板型別的右值引用,我們可以保持其對應實參的所有型別資訊。而使用引用引數(無論是左值還是右值)是的我們可以保持const屬性,以為在引用型別中的const是底層的。如果我們將函式引數定義為T1& 和T2&&,通過引用摺疊就可以保持翻轉實參的左值/右值屬性。 template<typename F, typename T1, typename T2> void flip( F f, T1 &&t1, T2 &&t2) {     f(t2,t1); } void f(int v1 , int &v2) {     cout<<v1<<" "<<++v2<<endl; } 但是上述版本不能用於接收右值引用引數的函式。 void g(int &&i, int& j) {     cout<<i << " " << j << endl; } flip(g, i, 42)// 錯誤不能從一個左值例項化int && 當用於一個指向模板引數型別的右值引用函式引數時,forward會保持實參型別的所有細節。(在utility中) template <typename F, typename T1, typename T2> void flip(F f, T1 &&t1, T2 &&T2) {     f(std::forward<T2>(t2), std::forward<T1>(t1)); } 同move一樣,不使用using宣告。 函式模板可以被另一個模板或普通非模板函式過載。 如果涉及函式模板,則函式匹配規則會在以下幾個方面受到影響:     對於一個呼叫,其候選函式包括所有模板實參推斷成功的函式模板例項。     候選的函式模板總是可行的,因為模板實參推斷會排除任何不可行的模板。     與往常一樣,可行函式(模板與非模板)按型別轉換來排序。當然,可以用於函式模板呼叫的型別轉換非常有限。     與往常一樣,如果恰有一個函式提供比任何其他函式都更好的匹配,則選擇此函式。但是如果有多個函式提供同樣好的匹配,則         如果同樣好的函式中只有一個是非模板函式,則選擇此函式。         如果同樣好的函式中沒有非模板函式,而有多個函式模板,且其中一個模板比其他模板更特例化,則選擇此模板。         否則,此呼叫有歧義。 正確定義一組過載的函式模板需要對型別間的關係及模板函式允許的有限的實參型別轉換有深刻的理解。 在定義任何函式之前,記得宣告所有過載的函式版本。這樣就不必擔心編輯器由於為遇到你希望呼叫的函式而例項化一個並非你所需的版本。  可變數目的引數被稱為引數包。兩種引數包:模板引數包:表示零個或多個模板引數;函式引數包,表示零個或多個函式引數。 我們用省略號來指出一個模板引數或函式引數表示一個包。在一個模板引數列表中,class... 或typename...指出接下來的引數表示零個或多個型別的列表;一個型別名後面跟一個省略號表示零個或多個給定型別的非型別引數的列表。在函式引數列表中,如果一個引數的型別是一個模板引數包,則此引數也是一個函式引數包。 template<typename T, typename... Args> void foo(const T &t, const Args& ...rest); 和往常一樣,編譯器從函式的實參推斷模板引數型別。對於一個可變引數模板,編譯器還會推斷包中引數的數目。 當我們需要知道包中有多少元素是,可以使用sizeof...運算子。 template<typename... Args> void g(Args... args) {     cout<<sizeof...(Args) <<endl;     cout<<sizeof...(args) <<endl; } 我們可以使用initializer_list來定義以個可接受可變數目實參的函式。但是,所有實參必須具有相同型別。 可變引數的函式通常是遞迴的。第一步呼叫處理包中的第一個實參,然後用剩餘實參呼叫自身。 template<typename T, typename...Args> ostream &print(ostream &os, const T &t, const Args&...rest) {     os<<t<<", ";     return print(os, rest...); } 當兩個函式提供同樣好的匹配。但是,非可變引數模板比可變引數模板更特例化,因此編譯器選擇非可變引數版本。 當定義可變引數版本的print時,非可變引數版本的宣告必須在作用域中,否則,可變引數版本會無限遞迴。 對於一個引數包,除了獲取其大小外,我們能對他做的危機的事情就是擴充套件。當擴充套件一個包時,我們還要提供用於每個擴充套件元素的模式。擴充套件一個包就是將它分解為構成元素,對於每個元素應用模式,獲得擴充套件後的列表。我們通過在模式右邊放一個省略號類出發擴充套件操作。 template <typename T,typename... Args> ostream & print(ostream &os, const T &t, const Args&... rest)   ///擴充套件Args {     os<< t << ", ";     return print(os, rest...); } 第一個擴充套件操作擴充套件模板引數包,為print生成函式引數列表。第二個擴充套件操作出現在對print的呼叫中。此模式為print呼叫生成引數類別。 對Args的擴充套件中, 編譯器模式將const Args& 應用到模板引數包Args中每個元素。 第二個擴充套件發生在對print的(遞迴)呼叫中 , 模式是函式包的名字。此模式擴展出一個由包中元素組成的、逗號分隔的列表。 c++語言還允許更復雜的擴充套件模式。如,我們可以編寫第二個可變引數函式, 對其每個引數呼叫debug_rep,然後呼叫print列印結果 template<typename...Args> ostream &errorMsg(ostream &os, const Args&... rest) {

相關推薦

C++11(15): 模板程式設計

面向物件程式設計和泛型程式設計都能處理在編寫程式時不知道型別的情況。不同之處:OOP能處理型別在程式執行之前都未知的情況;而泛型程式設計中,在編譯時就能獲知型別了 模板引數類別不能為空。 模板引數表示在類或函式定義中用到的型別或值。 template <typenam

effective C++筆記--模板程式設計(三)

文章目錄 請使用traits classes表現型別資訊 認識模板超程式設計 請使用traits classes表現型別資訊 . traits並不是C++的關鍵字或是預先定義好的構件,它們是一種技術,也是一個C++程式設計師共同遵守的協議

effective C++筆記--模板程式設計(二)

文章目錄 運用成員函式模板接受所有相容型別 需要型別轉換時請為模板定義非成員函式 運用成員函式模板接受所有相容型別 . 真實指標做的很好的一件事是支援隱式轉換,派生類的指標可以指向基類的指標,指向非常量物件的指標可以指向轉換成常量物件的指

effective C++筆記--模板程式設計(一)

文章目錄 瞭解隱式介面和編譯器多型 瞭解typename的雙重意義 學習處理模板化基類內的名稱 將與引數無關的程式碼抽離template 瞭解隱式介面和編譯器多型 . 面向物件程式設計世界總是以顯式介面和執行期多型解決問題。比

C++面試總結(三)模板程式設計

1.什麼是模板?    泛型程式設計是指獨立與任何型別的方式編寫程式碼。泛型程式設計和麵向物件程式設計,都依賴與某種形式的多型。面向物件程式設計的多型性在執行時應用於存在繼承關係的類,一段程式碼可以可以忽略基類和派生類之間的差異。在泛型程式設計中,編寫的程式碼可以用作多種型別

C/C++基礎--模板程式設計

模板引數 函式模板,編譯器根據實參來為我們推斷模板實參。 模板中可以定義非型別引數,表示一個值而非一個型別,這些值必須是常量表達式,從而允許編譯器在編譯時例項化模板。 非型別引數可以是整型,或者一個指向物件或函式的指標或(左值)引用。繫結到前者的實參必須是常量表達式,繫結到後者的必須具有靜態生存期

《Effective C++》模板程式設計:條款32-條款40

條款41:瞭解隱式介面和編譯期多型 class支援顯示介面和執行期多型 class的顯示介面由函式的名籤式構成(函式名稱、引數型別、返回型別) class的多型通過virtual函式發生在執行期 template支援隱式介面和編譯期多型 templa

如何寫出高效C++(模板程式設計)

對Effective C++讀了以後的總結(暑假沒事幹。。就是看書,從後往前的總結) 41。瞭解隱式介面和編譯器多型 隱式介面:由一組有效表示式構成,表示式要求了相應的約束條件。 顯式介面:則是在原始碼中明確可見的指出介面的約束條件(比如函式引數的型別)。 所謂的編譯期多

C++ primer 模板程式設計

繼續瀏覽c++ primer 看到模板與泛型程式設計這章,就順便把這幾節的程式碼綜合了下,對一個Queue佇列模板的實現 貼一下程式碼(看完書,自己敲,忘記了哪再看下書) #include <ostream> using std::ostream; //宣告Q

12.29--C++模板程式設計--《C++ Primer》學習

今天學習第16章《模板與泛型程式設計》 感覺腦子有點模模糊糊的,效率不是很高,趕快寫一下學習日誌備忘。 模板其實在java中用的也多了,但是C++的沒用過,感覺有點虛。 其實的確是差不多的用法,所以記幾個點好了。 1. 模板形參表,即template <typename

C++primer(第五版)》學習之路-第十六章:模板程式設計

【宣告:版權所有,轉載請標明出處,請勿用於商業用途。聯絡信箱:[email protected]】 16.1 定義模板 1.模板定義以關鍵字template開始,後跟一個模板引數列表,這是一個逗號分隔的一個或多個模板引數的列表,用小於號(<)和大於號(&

模板程式設計(二)--《C++ primer》

 通常在呼叫普通函式時,我們只要做到將函式的宣告放到其定義的前面,保證編譯器先掌握到函式的宣告,因此我們會把其函式宣告放到標頭檔案,而其定義放到原始檔當中;但是模板不同,為了生成一個例項化版本,編譯器需要掌握函式模板或類模板成員函式的定義,所以模板標頭檔案通常既包括宣告

C++ STL標準庫程式設計(一)

泛型程式設計,就是使用模板為主要工具來編寫程式。其中沒有太多的面向物件的觀念,不涉及虛擬函式的使用。 使用C++標準庫 C++標準庫:以程式碼形式給出,放於各種標頭檔案( header files )內,經過編譯後才能使用。 所有新式的 headers 內的元件封裝於 namespace

模板程式設計

       當我們希望可以用同一個函式處理不同型別的引數時(比如寫一個加法函式,可以處理各種不同型別的資料)都有哪些方法呢? 1、函式過載(同一作用域;函式名相同;引數列表不同) 缺點:         a、只要有新型別出現,就必須新增對應的函式         b、除了

C++知識點(十)程式設計C++STL標準模板

1.泛型程式設計 把程式碼從特定的資料結構中分離出來,使得它不依賴於特定的資料結構而更加通用 容器->迭代器->演算法 介面卡 2.概念:用於界定具備一定功能的資料型別 comparable:可比較 Assignable:可賦值 Sortable:可比較且可賦值 3.模型:符合一個

c++--模板編程

編譯 string std size_t har rom 數組大小 傳遞 成員函數 一、定義模板 1.1 函數模板 1. 適用情況:如果兩個函數幾乎是相同的,唯一的差異是參數的類型,函數體則完全一樣。 2. 定義 template <模板參數列表(以逗號分隔)&g

C++ Primer 第16章】《模板編程》目錄

cnblogs OS pan c++ get In lan microsoft .cn 模板與泛型編程 • 定義模板(16.1) 類模板(16.1.2) 類前置聲明範例 •【C

Effective C++: 07模板編程

單向 不可 允許 non-const 內容 卷標 基類 complete ear C++ template機制自身是一部完整的圖靈機(Turing-complete):它可以被用來計算任何可計算的值。於是導出了模板元編程(TMP, template metaprogramm

C++Primer_Chap16_模板程式設計_List03_過載和模板_筆記

  函式模板可以被另一個模板或普通非模板函式過載。與往常一樣,名字相同的函式必須具有不同數量和型別的引數。涉及函式模板,函式匹配規則會在以下幾方面受到影響: 對於一個呼叫,其候選函式包括所有模板實參推斷成功的函式模板例項 候選的函式模板總是可行的,因為模板實參推斷會排除任何

C++Primer_Chap16_模板程式設計_List02_模板實參推斷_筆記

  從函式實參類確定模板實參的過程稱為模板實參推斷(template argument deduction)。 型別轉換和模板型別引數   如果一個函式形參的型別使用了模板型別引數,那麼它採用特殊的初始化規則。只有很有限的幾種型別轉換會自動應用於這些實參。編譯器通常不是對