模板非型別形參的詳細闡述
關於模板的非型別形參,網上有很多內容,C++primer只有大概一頁的闡述,但是都不夠清晰詳細。下面我儘可能從自己的角度去給大家描述一下非型別形參的相關細節。如果想進一步理解非型別形參以及模板內容可以閱讀C++template這本書,在4.1節,8.3.3節,13.2節都有相關解釋。
模板除了定義型別引數,我們還可以在模板定義非型別引數。
什麼是非型別形參?顧名思義,就是表示一個固定型別的常量而不是一個型別。
先舉一個簡單的例子(模板類與模板函式都可以用非型別形參)
//例子1: template<class T,int MAXSIZE> class List{ private: T elems[MAXSIZE]; public: Print(){ cout<<"The maxsize of list is"<<MAXSIZE; } } List<int,5> list; list.Print();//列印"The maxsize of list is 5"
這個固定型別是有侷限的,只有整形,指標和引用才能作為非型別形參,而且繫結到該形參的實參必須是常量表達式,即編譯期就能確認結果。
這裡要強調一點,我們對於非型別形參的限定要分兩個方面看
1.對模板形參的限定,即template<>裡面的引數
2.對模板實參的限定,即例項化時<>裡面的引數
下面逐個解釋一下非型別形參的侷限
1.浮點數不可以作為非型別形參,包括float,double。具體原因可能是歷史因素,也許未來C++會支援浮點數。
2.類不可以作為非型別形參。
3.字串不可以作為非型別形參
4.整形,可轉化為整形的型別都可以作為形參,比如int,char,long,unsigned,bool,short(enum宣告的內部資料可以作為實參傳遞給int,但是一般不能當形參)
5.指向物件或函式的指標與引用(左值引用)可以作為形參
下面解釋一下非型別實參的侷限
1.實參必須是編譯時常量表達式,不能使用非const的區域性變數,區域性物件地址及動態物件
2.非Const的全域性指標,全域性物件,全域性變數(下面可能有個特例)都不是常量表達式。
3.由於形參的已經做了限定,字串,浮點型即使是常量表達式也不可以作為非型別實參
備註:常量表達式基本上是字面值以及const修飾的變數
//例子2: template<class T,int MAXSIZE> class List{ private: T elems[MAXSIZE]; public: void Print(){ cout<<"The maxsize of list is "<<MAXSIZE; } }; const int num1 = 9; ;//全域性變數 static int num2= 9; ;//全域性變數 const int num3 = 9; ;//區域性變數 List<int,num1> list; //正確 List<int,num2> list; //錯誤 List<int,num3> list; //正確 //再看一個關於指標和字串比較特別的例子 //例子3: template<char const* name> class pointerT{ }; char a[] = "saaa";;//全域性變數 char a2[] = "saaa";;//區域性變數,寫在main函式裡面 char *b = "saaa";//全域性變數 char *const c = "saaa";//全域性變數,頂層指標,指標常量 pointerT<"testVarChar"> p1;//錯誤 pointerT<a> p2;//正確 pointerT<a2> p22;//錯誤,區域性變數不能用作非型別引數 pointerT<b> p3;//錯誤,error C2975:“pointerT”的模板引數無效,應為編譯時常量表達式 pointerT<c> p4;//錯誤,error C2970: “c”: 涉及帶有內部連結的物件的表示式不能用作非型別引數
//關於指標常量和常量指標可以參考部落格
這裡大家可能會有幾個疑問
①.到底為什麼字串不能作為實參?
答:我們看到上面p1的模板實參是"testVarChar",然而當我們在另一個編譯單元(.cpp檔案)同樣宣告這麼一個模板例項時,這兩個"testVarChar"的地址可能是不同的,編譯器傳遞給模板時就會傳遞傳遞不同的地址,從而導致這兩個模板例項是兩個不同且不相容的型別。這就是支援字串的問題所在。(這裡可能更深的涉及模板的實現原理)
②.變數b和c作為模板實參為什麼錯誤不同?
答:首先解釋b實參,b在這裡看做是一個指標,是一個全域性指標,但是他不是一個常量表達式,所以b不對。我們再看看c,c相比於b對了一個const修飾符,表示這個指標是一個常量。然而const是一個比較特別的關鍵字,他具有內部連結屬性(關於內連線參考部落格理解C++的連結:C++內連結與外連結的意義),也就是說僅在定義這個變數的檔案內可見,不會造成不同編譯單元的混編時的連結錯誤。
這個特性對於模板來說可是有問題的,就像問題①所描述的,由於每個編譯單元可能都有一個c變數,導致在編譯時,例項化多個c,而且c的地址還不同,這就造成二個模板的例項是兩個不同且不相容的型別。
③為什麼a變數作為實參可以?
答:我看過一些書籍,上面舉得例子都是用const修飾不行的情況下在加extern來形成extern constchara[]="saaa";這樣形式的語句,extern和const聯合使用確實可以壓制const的內部屬性。
這個a這裡可以看做一個數組型別(進一步理解陣列與指標的關係可以參考部落格
),那麼我們看到他不僅避免了①中的例項化地址不同的問題(因為是全域性唯一的),而且還避免了const帶來的內部連結問題,所以這一項可能是經過編譯器優化過的結果。
如果有哪位大神能夠給出更合理的解釋,希望能留言一下,謝謝~