变量模板(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