- 作者: 雪山肥鱼
- 时间:20211228 07:32
- 目的: 可变参模板
可变参模板 从 C++11 引入,允许模板定义中包含 0到多个 任意模板。而c++17 也有相关新的特性
可变参模板引入
template<typename... T>//...代表 参数包
void myfunc(T... args) {//T:一堆类型,类型包,args:一堆形参,形参包,每个参数的类型是可以不相同的
cout << "--------begin---------" << endl;
cout << sizeof...(args) << endl;//收到的参数数量,sizeof...() 针对可变参固定写法 c++11 引入,针对的只能是...的可变参,()可以是类型,也可以是形参
cout << sizeof...(T) << endl;//收到的类型数
cout << "-------end-------" << endl;
}
int main() {
myfunc();
myfunc(10,20);
myfunc(10,25.8,"ab",68);
myfunc<double, double>(10,25,8,"ab",68,73);//后面3个事编译器推断出来的类型
return 0;
}
可变参模板的展开
可变参模板的展开套路比较固定。与C中的可变参略有不同。内部实现
c++11中的展开
/*
template<typename... T>
void myfunc(T... args) {
}
*/
void myfunc() {
cout<<"展开完毕"<<endl;
}
template<typename T, typename... U>
void myfunc(T fristarg, U...otherArgs) {
cout<<"收到的参数"<<<"firstarg"<<firstarg<<endl;
myfunc(otherArgs...);
}
int main() {
myfunc(10, "abc",12.7);
return 0;
}
可变参模板的展开,将类型T 和 参数 Args,整个挪到 template中。T 我认为在这里应该起到的是占位符的作用。因为 所有args的类型 不需要完全统一。
c++17 中的展开
涉及 if constexpr() 的使用
template<typename T, typename...U>
void myfunc(T firstarg, U...ortherAargs) {
cout<<"收到的参数"<<"firstarg"<<firstArgs<<endl;
cout<<"sizeof...(otherArgs)"<<sizeof...(otherAargs)<<endl;
if constexpr (sizeof...(otherAargs)) {
myfunc(otherAargs...);
}
}
int main() {
myfunc(10, "abc", 12.7);
return 0;
}
// 输出结果,2, 1, 0 每进来一次 都会 -1
注意注释中关于sizeof...() 的 阐述。每次进来会-1
用dumpbin 可以查看到,这个模板被实例化了3次
myfunc<int, char const *, double>(int, const char *,double)
myfunc<const char *, double>(const char * ,double);
myfunc<double>(double)
关于 if constexpr
// 关于 if constexpr
/*
1.
不管 if constexpr 条件是否成立,都会进行检查
这不同于 #ifdef, 不成立,则不会进入编译
if constexpr(sizeof...(otherargs) > 100)
{
testfunc()
}
*/
/*
2. 内容必须是常量,从执行期间,转移到了编译期间,则必须为常量
指定不行
int i = 0;
if constexpr(i > 0) {
}
*/
可变参模板
template<typename... T>
void myfunc(T... arg) {
cout << "myfunc(T...arg) is called" << endl;
}
template<typename... T>
void myfunc(T*... arg)
{
cout << "myfunc(T*...arg) is called" << endl;
}
void myfunc(int arg) {
cout << "myfunc(int arg) is called" << endl;
}
int main() {
//优先使用函数
myfunc(NULL);//int arg
myfunc(nullptr);//T arg
myfunc((int*)nullptr);//T*arg
return 0;
}
与函数模板一样,优先选择普通函数
折叠表达式
//可变参模板2 :折叠表达式 c++17 才引入
//特点:它与所有可变参有关,而不是与单独某个可变参有关,需要所有参数都参与计算
//1. 一元左折
template<typename... T>
auto sub_left(T... args) {
return (... - args);//圆括号不能省略,否则出错
}
//2. 一元右折
template<typename... T>
auto sub_right(T...args) {
return (args - ...);
}
//3. 二元左折,init 初始值
template<typename... T>
auto sub_left_t(T... args) {
return (220 - ... - args); //其中2个符号必须相同
}
//4. 二元右折
template<typename... T>
auto sub_right_t(T... args) {
return (args - ... - 220);
}
template<typename... T>
void print_val_left_b(T... args) {
(cout << ... <<args);//一定要有括号//cout<< 返回cout,第一次返回10,第二次abc,依此类推
}
int main() {
cout << sub_left(10, 20, 30, 40) << endl;//10-20-30-40 = -80;
cout << sub_right(10, 20, 30, 40) << endl;// (30-40) = -10, 20-(-10) = 30, 10 -30 = -20
cout << sub_left_t(10, 20, 30, 40) << endl;//120 (220-10) -20 -30 -40 = 120
print_val_left_b(10, "abc", 30, "def");//10abc30def 串起来了
cout << sub_left_t(10, 20, 30, 40) << endl;//120 (220-10) -20 -30 -40 = 120 (10 先被220 减)
cout << sub_right_t(10, 20, 30, 40) << endl;//200 10 -(20-(30-(40-220))) 初始值 就是说 40 先减掉220
return 0;
}
关注注释内容即可
/*
legacy
新需求: 每个都*2后 再求和
*/
template<typename... T>
auto print_result_1(T const &... args) {
(cout << ... << args) << "结束" << endl;
return (... + args);
}
template<typename... T>
auto print_result_2(T const &... args) {
(cout << ... << args) << "结束" << endl;
return (... + args);
}
template<typename... T>
void print_calc(T const&... args) {
cout << print_result_2(2 * args...);//可变参表达式
}
int main() {
print_calc(10, 20, 30, 40);
return 0;
}
prinnt_result_2(2*args...)即 可变参表达式
可变参类模板
递归继承方式展开 可变参
template<typename... Args>
class myclass {
public:
myclass() {
printf("myclass 泛化 is called, this = %p\n", this);
}
};
template<typename First, typename... Others>// 偏特化,单抽出来一个,与其他一包类型
class myclass<First, Others...> :private myclass<Others...> {
public:
myclass() :m_i(0)
{
printf("myclass 偏特化 is called, this = %p, sizeof...(Others) = %d\n", this, sizeof...(Others));
}
First m_i;
};
int main() {
//有3个,第一个为int, 那么剩下参数2个,一次类推,double 结束后,others 就为0.
//实例化顺序,先实例化泛化,再依次特化
myclass<int, float, double> myc;
return 0;
}
对于泛化可以用一个前置声明
template<typename... Args> class myclass;// 前向声明,非定义,成功的条件没有用声明创建对象。主要使用特化版本
只要不实例化就行,因为我们主要使用的是特化版本
1.PNG对于 这3个参数,从右往左拿,第一次拿出 double,others 为0, 第二次拿float, others 为1, 即double, 第三次拿 int, others 为2, 即 float,double
图片.png因为把 double 拿走后,参数个数为0, 所以,当参数为0时,使用泛化的构造函数,顺序如下:
图片.png
但是
当存在0个模板参数的特殊类模板时,不会选择泛化,而是选择0模板参数的类模板构造函数。
template<typename... Args>
class myclass {
public:
myclass() {
printf("myclass 泛化 is called, this = %p\n", this);
}
};
template<typename First, typename... Others>// 偏特化,单抽出来一个,与其他一包类型
class myclass<First, Others...> :private myclass<Others...> {
public:
myclass() :m_i(0)
{
printf("myclass 偏特化 is called, this = %p, sizeof...(Others) = %d\n", this, sizeof...(Others));
}
First m_i;
};
//0 个参数
template<>
class myclass<>//特殊的特化版本,0个 模板参数,看起来像全特化,但不是全特化,可变参模板不存在全特化
{
public:
myclass() {
printf("myclass() 0 个模板参数的泛化版本执行了,this = %p\n", this);
}
};
int main() {
myclass<int, float, double> myc;
return 0;
}
2.PNG
可变参数类模板,不存在全特化
带参数的构造函数
template<typename... Args>
class myclass {
public:
myclass() {
printf("myclass 泛化 is called, this = %p\n", this);
}
};
template<typename First, typename... Others>
class myclass<First, Others...> :private myclass<Others...> {
public:
myclass() :m_i(0)
{
printf("myclass 偏特化 is called, this = %p, sizeof...(Others) = %d\n", this, sizeof...(Others));
}
myclass(First parf, Others... paro) :m_i(parf) ,myclass<Others...> (paro...) {
cout << "------beigin------" << endl;
cout << "sizeof...(Others): " << sizeof...(paro)<<endl;
printf("myclass::myclass(First parf, Others... paro)泛化版本执行了, this = %p\n", this);
cout << "m_i = " << m_i << endl;
cout << "-------end--------" << endl;
}
First m_i;
};
template<>
class myclass<>
{
public:
myclass() {
printf("myclass() 0 个模板参数的泛化版本执行了,this = %p\n", this);
}
};
int main() {
myclass<int, float, double> myc(12,14.5,16.0);
return 0;
}
图片.png
其实就是递归继承。都去继承。特化,其实也就是 把一坨东西,拆成了,一个+一坨,然后不停的递归。因为是递归,所以第一次进去的,组后一个出来。
非类型模板参数
template <int... Args>
class myclass2
{
public:
myclass2() {
printf("myclass2() 泛化版本执行了, this = %p\n", this);
}
};
template<int First, int ...Others>
class myclass2<First, Others...>:private myclass2<Others...> {//这里的First, Others...都是具体的值了
public:
myclass2() {
printf("myclass2() 非类型模板参数 构造函数 is called this:%p, siezof...(Others) = %d, First = %d\n", this, sizeof...(Others), First);
}
};
int main() {
myclass2<11, 12, 13> my;
return 0;
}
图片.png
因为是 递归,所以参数从右往左。注意 此时在<11,12,13>都是具体的值,并不是类型啦。
模板模板参数包
//模板模板参数包的展开
template<
typename T,
template<typename> typename... Container>
class myclass3 {
public:
myclass3() {
printf("myclass3() 泛化版本 is called,this=%p\n", this);
}
};
template<
typename T,
template<typename> typename FirstContainer,
template<typename> typename... OtherContainers>
class myclass3<T, FirstContainer, OtherContainers...> :private myclass3<T, OtherContainers...> {
public:
myclass3() {
printf("myclass3() 模板模板参数的特例化 is called\n this = %p, sizeof...(OthersContainers)=%d\n", this, sizeof...(OtherContainers));
m_containers.push_back(12);
}
FirstContainer<T> m_containers;
};
template<typename T,
template<typename> typename... Container>
class myclass3_3 :private myclass3<T, Container...> {
public:
myclass3_3() {
printf("myclass3_3() 继承构造 is called\n this = %p, sizeof...(Container)=%d, T的类型是:%s\n", this,
sizeof...(Container), typeid(T).name());//boost库 type_id_with_cvr<>().pretty_name();
}
};
int main() {
myclass3_3<int, vector, list, deque> myc3;
return 0;
}
dumpbin 会实例化5个类
- myclass3<int>
- myclass3<int, deque>
- myclass3<int, list, deque>
- myclass3<int,vector, list, deque>
-
myclass3_3<int, vector, list, deque>
图片.png
实际上就是递归,最后拆到没有参数。就会调用泛化版本
通过递归组合方式展开参数包
组合方式,是一种have 的模式,A 类种 有B 的实例。
/*
class B {
};
class A {
public:
B b;
};
*/
老方式,定要特化跟着泛化的模式
template<typename ...Args>
class myclass {
myclass() {
printf("myclass() 泛化版本执行了,this = %p\n", this);
}
};
template<typename First, typename... Others>
//class myclass<First, Others...> :private myclass<Others...> {
class myclass<First, Others...> {
public:
myclass():m_i(0) {
printf("myclass() 偏特化版本 is called , this = %p, sizeof...(Others) = %d\n", this, sizeof...(Others));
}
//myclass(First parf, Others... paro) :m_i(parf), myclass<Others...>(paro)
myclass(First parf, Others... paro) :m_i(parf),m_o(paro...)//其实格式非常套路
{
cout << "------beigin------" << endl;
cout << "sizeof...(Others): " << sizeof...(paro) << endl;
printf("myclass::myclass(First parf, Others... paro)特化版本执行了, this = %p\n", this);
cout << "m_i = " << m_i << endl;
cout << "-------end--------" << endl;
}
First m_i;
myclass<Others...> m_o;// 此处新增
};
template<>
class myclass<>
{
public:
myclass() {
printf("myclass() 0 个模板参数的泛化版本执行了,this = %p\n", this);
}
};
int main() {
myclass<int, float, double> my(12, 13.5, 23.0);
return 0;
}
模式相对套路,去掉了 private 继承关系,去掉了 带参构造函数中对 class<Others...>的递归。
当然最后参数用光后,调用全空的 "全特化"
-
不同于继承递归:
this指针的不同
图片.png
说明弄出来4个对象
从对象关系来看
继承:
继承展开.png
组合:
组合.png
通过tuple元组方式展开
何为tuple?实际上是一个可变参的类模板. c++17 方能引入
int main() {
tuple<float, int, int> mytuple(12.5f,100, 24);
cout << get<0>(mytuple) << endl;
cout << get<1>(mytuple) << endl;
cout << get<2>(mytuple) << endl;
return 0;
}
基础模板:
template<typename... T>
void myfunc(const tuple<T...> & t) {
}
int main() {
tuple<float, int, int> mytuple(12.5f,100, 24);
cout << get<0>(mytuple) << endl;
cout << get<1>(mytuple) << endl;
cout << get<2>(mytuple) << endl;
cout << "---------------------" << endl;
myfunc(mytuple);
return 0;
}
实际上借助了中间的tuple可变参类模板进行遍历
- 扩展:
template<int count,int maxcount, typename... T>
class myclass{
public:
static void myfunc(const tuple<T...>&t) {
cout << "value= " << get<count>(t) << endl;
myclass<count + 1, maxcount, T...>::myfunc(t);
}
};
template<int maxcount, typename... T>
class myclass<maxcount, maxcount, T...> {
public:
static void myfunc(const tuple<T...> &t) {
}
};
template<typename... T>
void myTemplateFunc(tuple<T...> & t) {
myclass<0, sizeof...(T), T...>::myfunc(t);
}
int main() {
tuple<float, int, int>mytuple(12.6f, 100, 52);
myTemplateFunc(mytuple);
return 0;
}
tuple只做中间变量,tuple是现成的,要借助一个计数器,每处理一个参数,计数器+1,一直把所有参数处理完。当 count == maxcount时。
最后,提供一个模板偏特化,作为递归结束
基类参数包的展开
某个类的基类 也可以是可变参
即:可以有多个爹
//基类参数包的展开
template<typename... myClassPList>
class myclass : public myClassPList... {//存在一堆爹
public:
myclass() : myClassPList()...
{
cout << "myclass : myclass 5, this = " << this << endl;
}
};
class PA1
{
public:
PA1()
{
cout << "PA1() is called, this= " << this << endl;
}
private:
char m_s1[100];
};
class PA2
{
public:
PA2()
{
cout << "PA2() is called, this = " <<this<< endl;
}
private:
char m_s1[200];
};
class PA3
{
public:
PA3()
{
cout << "PA3() is called, this = " << this <<endl;
}
private:
char m_s1[300];
};
int main() {
myclass<PA1, PA2, PA3> obj;
cout << "sizeof(obj): " << sizeof(obj) <<endl;
return 0;
}
图片.png
可变模板的特化
没有全特化,只有特化
//泛化版本
template<typename... Args>
class myclass {
public:
myclass()
{
printf("myclass 泛化 is called, this = %p, sizeof...(Args) = %d\n", this, sizeof...(Args));
}
};
//偏特化版本
template<typename First, typename... Others>
class myclass<First, Others...> {
public:
myclass() {
printf("myclass<First, Others..>偏特化 is called, this =%p, sizeof...(Others) = %d\n", this, sizeof...(Others));
}
};
//偏特化版本
template<typename Args>
class myclass <Args> {
public:
myclass() {
printf("myclass<Args> 偏特化 is called, this = %p\n", this);
}
};
//偏特化版本
template<typename Arg1, typename Arg2>
class myclass<Arg1, Arg2>
{
public:
myclass() {
printf("myclass<Args1, Args2> 偏特化 is called, this = %p\n", this);
}
};
int main() {
myclass<int> myc1;
myclass<int, float> myc2;
myclass<int ,float, double> my3;
myclass<int, float, double, char> my4;
myclass<> my5;
return 0;
}
感觉很像某些语言,比如 erlang 里的匹配模式:
执行结果:
图片.png
注意,class<> 走的是泛化,因为
template<typename... Args>
class myclass {
}
本身就包括 0 -> n 个参数的变化
网友评论