變量模板(variable template)是C++2.0的一個新特性,雖然他功能強大,但是在平時的代碼中用得比較少。最近在侯捷老師的視頻里學到這個知識點,這里簡單說一下。
和C++模板一樣,變量模板也有函數模板和類模板,這種情況有非常多相似,就是作用對象不同。
那么變量模板這個“變量”體現在哪里?①參數個數可變 ②參數類型可變
也就是我,我們可以給一個函數傳進去一個亂七八糟的的一包東西,這包東西的元素個數和元素類型都是不確定的!這確實是一個激動人心的功能,有時候我們確實需要這一種特性,比如說我們希望設計一個參數個數不定的min/max函數,這時候參數可變就派上用場了。(當然其實只是實現可變參數的同類型函數,initializer_list就足夠了但是必須要加上一個花括號
侯捷老師的視頻里講到七個例子,這里選幾個來理解
首先是函數模板:
變量模板最簡單的用法,我們得思考既然這一包得個數和參數類型都是不確定得,那么我們怎樣才能精確地拿出里面的每一個元素呢?這里給出一個解決辦法:用遞歸。我們每一次遞歸中把一包元素分成一個和另外一包兩份,即假設本次這一包是n+1個元素,那么我們處理第一個元素,那么剩下n個元素還沒處理,我們就把這n個元素分成兩份:1+(n-1)這兩份,然后把這兩份繼續遞歸下去。那么下一層遞歸就是處理n-1中的第一個元素再繼續遞歸,最后總有處理完的那一天。
talk is cheap, show me the code,一看代碼就容易理解了。
1 #include<bits/stdc++.h> 2 using namespace std; 3 4 void printX() { 5 cout<<"All done"<<endl; 6 } 7 8 template<typename T,typename... Types> 9 void printX(const T& firstArg,const Types&... args) { 10 cout<<firstArg<<endl; 11 printX(args...); 12 } 13 14 int main() 15 { 16 printX(7.5,"hello",bitset<16>(377),42); 17 return 0; 18 }
OK,學懂了上面那個例子,我們也許想用變量模板做一些有趣的小東西,我們可以嘗試用變量模板模擬C語言的printf函數。
代碼有注釋,很好懂。
1 #include<bits/stdc++.h> 2 using namespace std; 3 4 //這個函數是處理最后沒有參數了 5 void Myprintf(const char *s) { 6 while (*s) { 7 if (*s=='%' && *(++s)!='%') //沒有參數卻還有%,參數不足 8 throw runtime_error("invalid format string: missing arguments"); 9 cout<<*s; 10 s++; 11 } 12 } 13 14 //這里是Args還有參數,那么繼續做 % 的解析 15 template<typename T,typename... Args> 16 void Myprintf(const char* s,T value,Args... args) { 17 while (*s) { 18 if ((*s)=='%' && *(++s)!='%') { //解析到一個%,消耗一個參數 19 cout<<value; 20 Myprintf(++s,args...); 21 return; 22 } 23 cout<<*s; //無關%的字符原樣輸出 24 s++; 25 } 26 throw logic_error("extra arguments provided to printf"); 27 } 28 29 int main() 30 { 31 Myprintf("%d args first:%d %s %lf\n",2,42,"Hello",3.1415926); 32 Myprintf("%d args first:%d %s %lf %d",2,42,"Hello",3.1415926); 33 return 0; 34 }
從這里可以看到,我們依然使用了遞歸。其實遞歸和變量模板的搭配將陪伴我們很久。
比如這里就用變量模板函數實現我們一開頭所說的:可變參數的max函數:非常簡潔小巧
1 #include<bits/stdc++.h> 2 using namespace std; 3 4 template<typename T> 5 T maximum(T n) { 6 return n; 7 } 8 9 template<typename T,typename... Args> 10 int maximum(T first,Args... args) { 11 return max(first,maximum(args...)); 12 } 13 14 int main() 15 { 16 cout<<maximum(12,2,94,41,36,1)<<endl; 17 return 0; 18 }
上面都是模板函數,現在到模板類。其實道理也一樣,都是個數和類型都可變,也都是遞歸處理:
1 #include<bits/stdc++.h> 2 using namespace std; 3 4 //下面的兩個struct都是模板類,函數里直接創建臨時變量調用函數 5 6 //盤點IDX然后輸出,然后創建新的struct就行遞歸 7 template<int IDX,int MAX,typename... Args> 8 struct PRINT_TUPLE { 9 static void print(ostream& os,const tuple<Args...>& t) { 10 os<<get<IDX>(t)<<(IDX+1==MAX ? "" : ","); 11 PRINT_TUPLE<IDX+1,MAX,Args...>::print(os,t); //注意IDX+1 12 } 13 }; 14 //什么都不干的PRINT_TUPLE,處理最后 15 template<int MAX,typename... Args> 16 struct PRINT_TUPLE<MAX,MAX,Args...> { 17 static void print(ostream& os,const tuple<Args...>& t) { 18 } 19 }; 20 21 //重載了<<運算符,重點是 PRINT_TUPLE 22 template<typename... Args> 23 ostream& operator << (ostream& os,const tuple<Args...>& t) { 24 os<<"["; 25 PRINT_TUPLE<0,sizeof...(Args),Args...>::print(os,t); 26 return os<<"]"; 27 } 28 29 int main() 30 { 31 cout<<make_tuple(7.5,string("hello"),42,bitset<16>(377)); 32 return 0; 33 }
接下來,我們要用變量模板實現tuple,我們有繼承版本和組合版本的,都是令人拍案叫絕的實現。
繼承版本的,構造函數把第一個元素(自己的)和后n-1個元素(繼承自父類),這樣不斷繼承不斷新增一個元素得到我們想要的個數。
其實原理不難理解,但是代碼實現上還是有許多技巧的。

1 #include<bits/stdc++.h> 2 using namespace std; 3 4 template<typename... Values> class tup; 5 6 //最基礎的那個基類,就是一個空的類 7 template<> 8 class tup<> { 9 }; 10 /* 11 tup<> 12 tup<int> 13 tup<int,double> 14 tup<int,double,string> 15 */ 16 template<typename Head,typename... Tail> 17 //<Head,Tail...>繼承自<Tail...> 18 class tup<Head,Tail...> : private tup<Tail...> { 19 private: 20 typedef tup<Tail...> inherited; //父類的類型 21 protected: 22 Head m_head; 23 public: 24 tup() {} 25 //那么構造函數把第一個元素(自己的)和后n-1個元素(繼承自父類) 26 tup(Head v,Tail... vtail) : inherited(vtail...),m_head(v) { 27 } 28 29 Head head() { 30 return m_head; 31 } 32 inherited& tail() { 33 return *this; 34 } 35 }; 36 37 int main() 38 { 39 tup<int,float,string> it1(42,66.3,"world"); 40 cout<<it1.head()<<endl; 41 cout<<it1.tail().head()<<endl; 42 cout<<it1.tail().tail().head()<<endl; 43 return 0; 44 }
組合版本的
1 #include<bits/stdc++.h> 2 using namespace std; 3 4 template<typename... Values> class tup; 5 6 //最基礎的那個基類,就是一個空的類 7 template<> 8 class tup<> { 9 }; 10 /* 11 tup<> 12 tup<int> 13 tup<int,double> 14 tup<int,double,string> 15 */ 16 template<typename Head,typename... Tail> 17 class tup<Head,Tail...> { 18 private: 19 typedef tup<Tail...> composited; 20 protected: 21 composited m_tail; //注意這里,組合的關鍵 22 Head m_head; 23 public: 24 tup() {} 25 //那么構造函數把第一個元素和后n-1個元素(新組合) 形成組合 26 tup(Head v,Tail... vtail) : m_tail(vtail...),m_head(v) { 27 } 28 29 Head head() { 30 return m_head; 31 } 32 composited& tail() { 33 return m_tail; 34 } 35 }; 36 37 int main() 38 { 39 tup<int,float,string> it1(41,6.3,"hello"); 40 cout<<it1.head()<<endl; 41 cout<<it1.tail().head()<<endl; 42 cout<<it1.tail().tail().head()<<endl; 43 return 0; 44 }
參考資料:
侯捷老師的C++2.0課程:https://www.bilibili.com/video/BV11x411Z7zk
