C++2.0特性之一:變量模板(variable template)


變量模板(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 }
例子2

 

 

從這里可以看到,我們依然使用了遞歸。其實遞歸和變量模板的搭配將陪伴我們很久。

比如這里就用變量模板函數實現我們一開頭所說的:可變參數的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 } 
例子3

 

 

 

上面都是模板函數,現在到模板類。其實道理也一樣,都是個數和類型都可變,也都是遞歸處理:

 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 }
例子4

 

 

接下來,我們要用變量模板實現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 }
例子5

 

 

組合版本的

 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 } 
例子6

 

 

 

參考資料:

侯捷老師的C++2.0課程:https://www.bilibili.com/video/BV11x411Z7zk


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM