-
可变参数模板
2020-04-12 10:42:38可变参数模板 C++11引进了一个新功能:Variadic Template Function (可变参数模板)。通过可变参数模板,我们可以创建出接受任意类型、任意个数参数的函数。 如何使用 一个声明可变参数模板的简单例子: template<...可变参数模板
C++11引进了一个新功能:
Variadic Template Function
(可变参数模板)。通过可变参数模板,我们可以创建出接受任意类型、任意个数参数的函数。如何使用
一个声明可变参数模板的简单例子:
template<typename T, typename ... Args> void log(T first, Args ... args);
上面的函数可以接受1至多个参数,
Arg...
代表着不同个数的模板参数。声明一个可变参数模板函数很简单,但是定义它需要一点技巧。我们不能直接访问传进的不同数量的参数。我们需要使用c++类型解析机制以及递归来实现这一点:
template<typename T, typename ... Args> void log(T first, Args ... args) { cout<<first<<" , "; //打印第一个参数 log(args ...); //递归 } void log(){ return; };
原理
接下来让我们看看调用
log
时发生了什么log(2, 2.2, "2.2");
以上代码中,三个参数的类型分别为
int
,double
,const char*
编译会根据三个参数的类型以及我们声明的可变参数模板,创建一下的可变参数函数:void log(int first, double b, const char *c){ cout<<first<<","; log(b,c); }
这样,第一个参数被打印了出来,剩余的两个参数继续传入另外的一个可变参数模板中,跟之前一样,编译器继续创建一个可变参数函数:
void log(double first, const char*c){ cout<<first<<endl; log(c); }
接下来:
void log(const char* first){ cout<<first<<endl; log(); }
由于只有一个参数,没有多余的参数可以传递给log(),因此我们需要定义一个没有参数的log(),调用直接返回,以此来结束递归。
-
C++ 可变参数模板
2021-01-07 21:31:40可变参数模板可变参数模板参数包示例1示例2参考资料 可变参数模板 一个可变参数模板(variadic template)就是一个接受可变数目参数的函数模板或类模板。 参数包 可变数目的参数被称为参数包(parameter packet)。 存在...可变参数模板
一个可变参数模板(variadic template)就是一个接受可变数目参数的函数模板或类模板。
参数包
可变数目的参数被称为参数包(parameter packet)。
存在两种参数包:模板参数包(template parameter packet),表示0个或多个模板参数;函数参数包(function parameter packet),表示0个或多个函数参数。
采用省略号(…)的形式来指出这是一个参数包。
在模板参数列表中,class…或typename…指出接下来的参数表示0个或多个类型的列表。一个类型名后面跟省略号(T…)表示0个或多个给定类型T的非类型参数的列表。在函数参数列表中,如果一个参数的类型是一个模板参数包,则此参数也是一个函数参数包。
eg:
//Args 是一个模板参数包;rest是一个函数参数包 //Args 表示0个或多个模板参数类型 //rest 表示0个或多个函数参数 template<typename T,typename... Args> void foo(const T &t, const Args& ... rest);
声明了foo函数是一个可变参数函数模板,它有一个名为T的类型参数和一个名为Args的模板参数包。
示例1
template<typename T,typename... Args> void foo(const T &t, const Args& ... rest) { cout << sizeof...(Args) << ends; cout << sizeof...(rest) << ends; } void test01() { int i = 10; double d = 3.14; string s = "hello"; foo(i, s, 42, d);//包中有3个参数 foo("hi");//包中有0个参数 }
结果:
编译器为foo实例化了两个不同的版本:void foo(const int&,const string&,const int&,const double&); void foo(const char[3]&);
每个实例的T类型都是第一个实参的类型,剩下的是参数包。
sizeof...
运算符可以用来查看包中元素个数。示例2
通过递归调用实现可变参数函数print.
template<typename T> ostream& print(ostream &os,const T &t) { return os << t; } template<typename T, typename... Args> ostream& print(ostream &os, const T &t, const Args&... rest) { os << t << " "; return print(os, rest...); } void test02() { int i = 0; double d = 3.14; string s = "hello"; print(cout,i,s,42,d,'\n'); }
结果:
第一个版本的print是一个普通的函数模板,它接受一个输出流和一个T类型,其的功能是输出T,并返回输出流的引用。其负责终止递归并打印初始调用中的最后一个实参。第二个版本是一个可变参数函数模板,它打印绑定到t的实参,并调用自身打印参数包中剩下的值。
在每一次调用return print(os, rest...);
时,rest中的第一个包中的实参被绑定到t,剩下的形成下一个print调用的参数包。
那么,在调用print(cout,i,s,42,d,'\n');
时,会发生如下递归调用:
调用 t rest… print(cout,i,s,42,d,’\n’) i s,42,d,’\n’ print(cout,s,42,d,’\n’) s 42,d,’\n’ print(cout,42,d,’\n’) 42 d,’\n’ print(cout,d,’\n’) d ‘\n’ print(cout,’\n’) 调用普通版本 对于最后一次调用,编译器会选择更加特化的普通版本。
注意:普通版本和可变参数版本要在同一作用域,否则会无限递归。
包扩展(展开)
对于一个参数包,除了获取其大小以外,唯一能对其进行的操作就是展开(expand)它。当展开一个包时,我们要提供用于每个扩展元素的模式(pattern)。展开一个包就是将它分解成构成的元素,对每个元素应用模式,获得展开后的列表。通过在模式右边放一个省略号(…)来触发展开操作。
eg:template<typename T, typename... Args> //展开Args ostream& print(ostream &os, const T &t, const Args&... rest) //展开rest
第一个展开操作展开模板参数包,为print生成函数参数列表。第二个扩展操作出现在对print的调用中。此模式为print调用生成实参列表。
对Args的展开中,编译器将模式const Args& 应用到模板参数包Args中的每个元素。因此,此模式的扩展结果是一个逗号分隔的0个或多个类型的列表,每个类型形如const type& 。
例如:print(cout,i,s,42,d,'\n'); //参数包大小为4
最后4个实参的类型和模式一起确定了未知参数的类型。此调用被实例化为:
ostream& print(ostream &, const int&, const string&, const int&, const double&, const char&);
第二个扩展发生在对print的递归调用中。在此情况下,模式是函数参数包的名字(即rest)。此模式扩展出一个由包中元素组成的、逗号分隔的列表。因此,这个调用等价于
print(os,s,42,d,'\n');
示例3
使用较为复杂的扩展模式。
//... print 的两个版本 template<typename T> string ossPrint(const T &t) { ostringstream oss; oss << t; return oss.str(); }; template<typename... Args> ostream &msg(ostream &os, const Args&... rest) { return print(os, ossPrint(rest)...); } void test03() { int i = 0; double d = 3.14; string s = "hello"; msg(cout, i, s, 42, d, '\n'); }
此处使用的模式是ossPrint(rest),这个模式表示对包中的每一个元素调用ossPrint。注意省略号的位置。是在ossPrint(rest)…而不是ossPrint(rest…),其原因是ossPrint并不接受一个可变参数包,它只会接受一个普通类型参数。
参考资料
《C++ Primer 第5版》
-
模板-可变参数模板展开
2020-03-27 16:26:00模板-可变参数模板展开 一、可变参数模板 C++11增强了模板功能,在C++11之前,类模板和函数模板只能含有固定数量的模板参数,现在C++11中的新特性可变参数模板允许模板定义中包含0到任意个模板参数。可变参数模板和...模板-可变参数模板展开
一、可变参数模板
C++11增强了模板功能,在C++11之前,类模板和函数模板只能含有固定数量的模板参数,现在C++11中的新特性可变参数模板允许模板定义中包含0到任意个模板参数。可变参数模板和普通模板的语义是一样的,只是写法上稍有区别,声明可变参数模板时需要在typename或class后面带上省略号“…”。
省略号的作用有两个:
- 声明一个参数包,这个参数包中可以包含0到任意个模板参数。
- 在模板定义的右边,可以将参数包展开成一个一个独立的参数。
二、可变参数模板函数
1、定义
template <class... T> void f(T... args){ cout << sizeof...(args) << endl; //打印变参的个数 } f(); // 0 f(1, 2); // 2 f(1, 2.5, ""); // 3
2、模板参数包展开方式
1)递归函数方式展开参数包
通过递归函数展开参数包,需要提供一个参数包展开的函数和一个递归终止函数,递归终止函数正是用来终止递归的,来看下面的例子。
#include <iostream> using namespace std; // 递归终止函数 void print(){ cout << "empty" << endl; } /* 或者 template <class T> void print(T t){ cout << t << endl; } 或者 template<typename T,typename T1, typename T2> void print(T t, T1 t1){ cout<<t<<""<<t1 <<endl; } 或者 void print(T t, T1 t1, T2 t2){ cout<<t<<""<<t1<<""<<t2<<endl; } */ // 展开函数 template <class T, class ...Args> void print(T head, Args... rest){ cout << "parameter " << head << endl; print(rest...); } int main(void){ print(1,2,3,4); return 0; }
上例会输出每一个参数,直到为空时输出empty。有两个函数,一个是递归函数,另一个是递归终止函数,参数包Args…在展开的过程中递归调用自己,每调用一次参数包中的参数就会少一个,直到所有的参数都展开为止,当没有参数时,则调用非模板函数fun终止递归过程。
递归调用的过程如下:
print(1,2,3,4); print(2,3,4); print(3,4); print(4); print(); /* 或者 print(1,2,3,4); print(2,3,4); print(3,4); print(4); */
还可以通过std::tuple和std::enable_if方式:
template<std::size_t I = 0, typename Tuple> typename std::enable_if<I == std::tuple_size<Tuple>::value>::type printtp(Tuplet){ } template<std::size_t I = 0, typename Tuple> typename std::enable_if<I < std::tuple_size<Tuple>::value>::type printtp(Tuplet){ std::cout << std::get<I>(t) << std::endl; printtp<I + 1>(t); } template<typename... Args> void print(Args... args){ printtp(std::make_tuple(args...)); }
在上面的代码中,通过std::enable_if来选择合适的重载函数打印可变模版参数,基本思路是先将可变模版参数转换为tuple,然后通过递增参数的索引来选择print函数,当参数的索引小于总的参数个数时,会不断取出当前索引位置的参数并输出,当参数索引等于总的参数个数时终止递归。
2)逗号表达式和初始化列表方式展开参数包
递归函数展开参数包是一种标准做法,也比较好理解,但也有一个缺点,就是必须有一个重载的递归终止函数,即必须有一个同名的终止函数来终止递归,这样会感觉稍有不便。有没有一种更简单的方式,直接展开参数包呢?其实还有一种方法可以不通过递归方式来展开参数包,这种方式需要借助逗号表达式和初始化列表。
template <class ...Args> void expand(Args... args){ std::initializer_list<int>{(printarg(args), 0)...}; } 或者 template<typename... Args> void expand (Args... args){ std::initializer_list<int>{([&]{cout << args << endl; }(), 0)...}; }
{(printarg(args),0)…}将会展开成((printarg(arg1),0),(printarg(arg2),0),(printarg(arg3),0),etc…)
三、可变参数模板类
2、参数包展开方式
1)模板递归和特化方式展开参数包
可变参数模板类的展开一般需要定义2~3个类,包括类声明和特化的模板类。如下方式定义了一个基本的可变参数模板类,这个sum类的作用是在编译期计算出参数包中参数类型的size之和,通过sum<int,double,short>::value就可以获取这3个类型的size之和为14。
- 普通三段式
//前向声明 template<typename... Args> struct Sum; //定义 template<typename First, typename... Rest> struct Sum<First, Rest...>{ enum { value = Sum<First>::value +Sum< Rest...>::value}; }; //特化终止递归 template<typename Last> struct Sum<Last>{ enum { value = sizeof (Last) }; }; /* 或者 最后2个参数结束 template<typename First, typename Last> struct sum<First, Last>{ enum{ value = sizeof(First) +sizeof(Last) }; }; 或者 最后0个参数结束 template<> struct sum<> { enum{ value = 0 }; }; */
- 也可改成两段式
//定义 template<typename First, typename... Rest> struct sum{ enum { value = Sum<First>::value+Sum< Rest...>::value }; }; //特化 template<typename Last> struct sum<Last>{ enum{ value = sizeof(Last) }; };
还可以通过std::integral_constant来消除枚举定义value,利用std::integral_constant可以获得编译期常量的特性,可以将前面的sum例子改为这样:
// 前向声明 template<typename... Args> struct sum; // 基本定义 template<typename First, typename... Rest> struct sum<First, Rest...> : std::integral_constant<int, sum<First>::value + sum<Rest...>::value> { }; // 递归终止 template<typename Last> struct sum<Last> : std::integral_constant<int, sizeof(Last)>{ }; sum<int,double,short>::value;// 值为14
-
C11-可变参数模板-Variadic Templates可变参数模板
2020-06-01 14:05:59/********************************... ************************* 1.Variadic Templates可变参数模板 ************************* *******************************************************************************./************************************************************************************** ************************* 1.Variadic Templates可变参数模板 ************************* ***************************************************************************************/ namespace BaseFeatures_NameSpace { //此处一定要有一个无参的ShowInformation()定义或者声明在可变参数模板版的前面 //该函数结束编译期递归,在解包参数为0时调用 void ShowInformation() { cout << endl; } /* 用法: hello(); template<typename T,typename... args> hello(T InParam1,Types&... args) { hello(...args);//递归 } */ template<typename T, typename... Types> void ShowInformation(const T& firstArg, const Types&... args) { cout << "编译期递归中" << endl; cout << firstArg << endl; ShowInformation(args...); } template<typename... Types> void ShowInformation(const Types&... args) { cout << "编译期递归中,我比较没存在感,所以有楼上就不调我" << endl; ShowInformation(args...); } template<typename T> void ShowInformation(const T& firstArg) { cout << "偶是非递归的额外测试,测试能不能拦截下来递归,但是失败了" << firstArg << endl; } /************************************************************************************** ************************************ 1.1实例 ************************************ ***************************************************************************************/ //利用重载解决问题 //数据解析成对象 class VarTemplateExample_Data { int m_iID; int m_iData; }; class VarTemplateExample { public: const char* m_strInstanceName; int m_InstanceID; //vector<float> m_dataContainer; vector<VarTemplateExample_Data> m_dataContainer; template< typename... Types> void ResolveInformation(const char* In_strInstanceName,int In_ID, Types&... args) { cout << "ResolveInformation (const char* In_strInstanceName, T a, const Types&... args)" << endl; m_strInstanceName = In_strInstanceName; m_InstanceID = In_ID; ResolveInformation(args...); } template<typename... Types> void ResolveInformation(int In_ID, Types&... args) { const char* name = "DefaultName"; cout << "ResolveInformation(int In_ID,const Types&... args)" << endl; ResolveInformation(name, In_ID, args...); } template<typename... Types> void ResolveInformation(VarTemplateExample_Data& Obj, Types&... args) { cout << "ResolveInformationVarTemplateExample_Data Obj, const Types&... args" << endl; m_dataContainer.push_back(Obj); ResolveInformation( args...); } //只有通过特别指定的方式才能被调用 template<typename T> void ResolveInformation(VarTemplateExample_Data& firstArg) { cout << "ResolveInformation(const T& firstArg)" << endl; cout << "偶是非递归的额外测试,测试能不能拦截下来递归,但是失败了" << endl; } void ResolveInformation() { cout << endl; } private: }; } { BaseFeatures_NameSpace::VarTemplateExample temp; BaseFeatures_NameSpace::VarTemplateExample temp1; BaseFeatures_NameSpace::VarTemplateExample temp2; BaseFeatures_NameSpace::VarTemplateExample temp3; BaseFeatures_NameSpace::VarTemplateExample_Data obj1; BaseFeatures_NameSpace::VarTemplateExample_Data obj2; BaseFeatures_NameSpace::VarTemplateExample_Data obj3; BaseFeatures_NameSpace::VarTemplateExample_Data obj4; int i = 0; const char* str = "Test1"; SHOW_SMALL_FUNCTION_BLOCK_LIST_TIPS("不同参数个数的") temp.ResolveInformation(str,1,obj1,obj2); std::cout << endl; std::cout << endl; temp1.ResolveInformation( 1, obj1, obj2); std::cout << endl; std::cout << endl; temp2.ResolveInformation( obj1, obj2); std::cout << endl; std::cout << endl; //只有通过这种方式才会被调用 temp3.ResolveInformation<BaseFeatures_NameSpace::VarTemplateExample_Data&>(obj1); std::cout << endl; std::cout << endl; }
-
C++可变参数模板
2019-07-12 14:14:00可变参数模板 原文链接: http://blog.csdn.net/xiaohu2022/article/details/69076281 普通模板只可以采取固定数量的模板参数。然而,有时候我们希望模板可以接收任意数量的模板参数,这个时候可以采用可变参数模板... -
c++可变参数模板
2021-01-17 18:13:29可变参数模板 参数包: 10)模板参数包: template<typename T,typename… Args> Args为模板参数包,class…或typename…指出接下来的参数表示零个或多个类型的列表,一个类型名后面跟一个省略号表示零个或多个... -
c++实现可变参数模板函数_C ++中的可变参数模板:实现简单的元组
2020-06-27 10:00:11c++实现可变参数模板函数 从C ++ 11开始, std :: tuple是对Modern C ++的不可思议的扩展,它提供了固定大小的异构值集合。 不幸的是,元组在以传统方式进行管理时可能有些怀疑。 但是,随后发布的C ++标准引入了... -
C++11 可变参数模板
2020-11-24 23:49:59一个可变参数模板是一个接受可变数目参数的模板函数或模板类。可变数目的参数成为参数包。存在两种参数包:模板参数包 ,表示零个或多个模板参数;函数参数包,表示零个或多个函数参数。 用一个省略号来指出一个模板... -
C++模板与泛型编程:可变参数模板
2020-03-13 16:39:53文章目录可变参数模板sizeof... 运算符编写可变参数函数模板包扩展理解包扩展转发参数包 (emplace_back实现) 可变参数模板 一个可变参数模板就是接受一个可变数目参数的模板函数或模板类。可变数目的参数被称为...
-
Kotlin协程极简入门与解密
-
跟我练内测小分队学习礼包
-
容器相关操作
-
674. 最长连续递增序列
-
leetcode874. 模拟行走机器人 详解(新手向)
-
c++ STL阐述了各种查找算法的异同以及使用他们的时机
-
Spring Boot2.X仿朋友圈PC版系统实战_架构1.0
-
JDK1.8安装程序.zip
-
手势解锁-canvas-javascript实战
-
Java无损导出及转换word文档
-
【数据分析-随到随学】机器学习模型及应用
-
autograd_learning.ipynb
-
Matplotlib bar 柱状图
-
NC 同一个表,执行insert生效,执行update无效
-
2021最新Kubernetes(k8s)集群实战精讲
-
【数据分析-随到随学】Hive详解
-
软件设计实践.doc
-
【2021】UI自动化测试Selenium3
-
java微服务常用技术整合
-
Python的输入与输出