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
",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

原文地址:https://www.cnblogs.com/clno1/p/13066631.html