跟我学c++中级篇——c++11中的模板变参化
admin
2024-03-19 04:49:01
0

一、变参模板(Variadic Template)

变参,顾名思意,就是变量是可以动态变化的。变参模板,那么模板中的类型也是可以变化的。而模板又有类模板和函数模板,所以就知道,函数模板和类模板中的参数类型是可以变化的。这有什么用处呢?在设计和开发过程中,经常遇到一些无法确定参数的情况,这时候儿,最简单的方法就是,有一个参数不同,就写一个函数,然后随着时间的流逝就会发现,同样的函数会写很多,只是参数类型或者数量不同罢了。不要以为这是开玩笑,在微软的早期的库里,大量这样的函数,有的函数参数甚至到十来个。那么此时,就可以使用变参函数(模板)来实现。同样,类也是如此。
但是不是这种情况一一定要这么做,就得看实际的情况中综合考虑了。变参函数最有名的就是C语言中的printf,它的实现机制在前面分析过,有兴趣的可以翻一下它的三种实现机制。另外一个需要说明的是从c++11起,模板中提供了默认参数(template class A;),这个要和非类型模板参数区分开来。

二、变参函数模板

还是先从变参函数模板开始,先看一个具体的样子有一个形象化的认知:

template 
void TestVarTemplate(T... args)
{std::cout<< "args len is:"<< sizeof...(args) << std::endl;
}
template
void TestVarTemplate(T t, Args... args)
{
std::cout<< "t is:"<

看到那三个点没,这说明这里的模板参数类型是可以动态增加的,上面的这个例子,代表数量可以是0~N个,如果想避免出现0个的情况,就看第二个函数例子。变参函数模板有以下几种解析方式:
1、递归展开
我们仍然用前面的例子:

#include 
int TestVT()
{return 1;
}
//这个函数注释后会有什么效果?对比一下返回值和终止的位置
template
int TestVT(T t)
{return  t;
}
template
int TestVT(T var0, Args... varn)
{ std::cout << "args len is:" << sizeof...(varn) << std::endl;return var0 + TestVT(varn...);
}
int main()
{std::cout << "value is:"<< TestVT(1, 2, 3)<< std::endl;system("pause");return 0;
}

在代码中提出了问题,结果是,如果注释掉模板函数,那么调用一直会延伸到普通函数TestVT,多展开一次。而如果有这个模板函数,则调用到此模板函数即终止递归。如果不太明白,一个是要看一下递归是什么,另外一个明白递归后,下断点调试一下代码,立刻就清楚了。这也是后面提到的递归结束的标志控制方式。

2、打包推导(逗号表达式)展开
打包展开的代码示例:

template < typename T>
int Add(T arg)
{return    arg;
}template < typename... Args>
int AddValue(Args ... args)
{//也可使用类似int v[] = { (Add(args),0)... };这种逗号表达式代码,只是强制存储0到V数组中int v[] = { Add(args)... };int d = 0;for (auto n : v){d += n;}return    d;
}int TestAdd()
{std::cout << "-----"<< AddValue(3, 3, 3) << std::endl;;return 0;
}
int main()
{TestAdd();system("pause");return 0;
}

其实打包展开类似于一种循环展开调用函数或者递推进行。结果是一样的,可以和上面的递归方式对比。

3、c++17折叠表达式展开(C++17 fold expression)
这个是c++17支持的:

template 
int Add1(T t) {std::cout << "cur value:" << t << std::endl;return t;
}template 
void AddValue1(Args... args)
{//逗号表达式+初始化列表//int v[] = { (Add1(args),0)... };int d = (Add1(args)+ ...);std::cout << "cur sum:" << d << std::endl;
}void TestExp()
{AddValue1(1, 2,7);
}
int main()
{TestExp();return 0;
}

这种折叠表达式可以进一步去看c++17的标准,回头会专门找机会详细说明一下。

4、Tuple展开
看一下复用Tuple展开来实现的过程:

template 
void DoTuple(T&& tp, F&& func)
{// c++ 14 的 make_index_sequenceDoExecFunc(std::forward(tp), std::forward(func), std::make_index_sequence::value> {});
}template 
void DoExecFunc(T&& tp, F&& func, std::index_sequence)
{//此处的0仍然是为了填充整形数组std::initializer_list { (func(std::get(tp)), 0)... };
}void TestTuple()
{//注意auto的用法c++14DoTuple(std::make_tuple(1.1, 3, "OK"), [](const auto& value) -> void { std::cout << value << std::endl; });
}
int main()
{TestTuple();return 0;
}

通过上述的代码其实就可以清晰的看到一个可变参数函数模板是如何运作的。知其然,然后知其所以然。

三、变参类模板

看了变参函数模板,下面分析一下变参类模板如何实现:
1、递归组合展开
看一下相关代码:

template class Base1;template<> class Base1<> {};
template
class Base1 //: private Base1
{
public:Base1(T t, N... n) : t_(t), b_(n...)/*Base1(n...)*/ {Display();}void Display() { std::cout << t_ << std::endl; }
protected:T t_;Base1 b_;
};void TestClassCall() {Base1 b('a', 2, "this is test");
}
int main()
{TestClassCall();return 0;
}

这个方式其实和下面的递归继承展开都是用的递归,只是实现手段有所不同。

2、递归继承展开
这个需要继承一个基础的模板类,再进行展开:

template class Base;template<> class Base<> {};
template
class Base: private Base
{
public:Base(T t, N... n) : t_(t), Base(n...) {Display();}void Display() { std::cout << t_ << std::endl; }
protected:T t_;
};
void TestClassCall() {Base b('a', 2, "this is test");
}int main()
{TestClassCall();return 0;
}

这个和函数类似,通过不断的展开最终继承到一个空的模板中后,得到整体的继续体系实现展开。在c++11中使用的就是这种展开方式。

3、Tuple展开
这个和函数稍有不同:

template
class Base2
{
public:static void Display(const std::tuple& t){std::cout << "value = " << std::get(t) << std::endl;Base2::Display(t);  }
};//偏特化
template 
class Base2< max, max, T...>
{
public:static void Display(const std::tuple& t){std::cout << "is end" << std::endl;}
};template 
void DisplayFunc(const std::tuple& t)  //可变参函数模板
{Base2<0, sizeof...(T), T...>::Display(t);
}void CallTuple()
{std::tuple t(12.5f, 100, 52);DisplayFunc(t);}
int main()
{CallTuple();return 0;
}

4、模板终止递归(偏特化)
上面1有了一个空模板例子终止递归,下面再给一个模板参数终止递归的例子:

//偏特化
template
class PartialSpec {
public:enum {value = PartialSpec::value + PartialSpec::value//value =  PartialSpec::value //或者此处};};//终止递归0--这个需要修改基本定义处不能有一个参数,即只能有变参
template <> class PartialSpec<>
{ 
public:enum { value = 0 }; 
};
//递归调用终止的偏特化--1
template
class PartialSpec {
public:enum { value = sizeof(E) };
};//递归调用终止的偏特化--2
template
class PartialSpec
{
public:enum { value = sizeof(T) + sizeof(R) };
};void CallPs()
{PartialSpec ps;std::cout << ps.value << std::endl;
}

需要说明的是无论是函数模板还是类模板,都可以通过指定的条件来终止递归,可以是0,1,2…个参数(上面的终止递归可分别注释测试)。这个一定要引起注意。可以看一个1和4两个例程,一个是0个,一个是1个。当然也可以继续写其它参数数量的。也就是说,只要写一个变参模板,只要处理好结束的情况,也就是递归终结的情况,都可以实现。掌握了这一点,就明白了网上各种变参的编写模式的不同的原因了。

5、基类变参
在c++中,基类也可以是变参,如下代码:

class B1 {
public:void Display1() {std::cout << "This is B1." << std::endl;}
};class B2 {
public:void Display2() {std::cout << "This is B2" << std::endl;}
};template
class BN : public Bases...
{};void CallMul() 
{BN b;b.Display1();b.Display2();
}
int main()
{CallMul();return 0;
}

不过多重继承一般来说,是在开发中禁止使用的。这玩意儿坑太多,所以这个可以忽略,但要明白这么做是可以的就行。

四、总结

经过分析可以知道,技术总是在进步,开发的实际情况不断的变化,新需求的不断提出,这些都是推动技术不断向前的一个重要前提。越是活跃就越是不稳定,越是不稳定,就越欣欣向荣。所以在c++的技术迭代过程中可以看到不断的吸取Boost或者其它相关的提交的新的技术。唯有与时俱进,才不会被淘汰。
技术,人,社会,国家莫不如是。

相关内容

热门资讯

linux入门---制作进度条 了解缓冲区 我们首先来看看下面的操作: 我们首先创建了一个文件并在这个文件里面添加了...
C++ 机房预约系统(六):学... 8、 学生模块 8.1 学生子菜单、登录和注销 实现步骤: 在Student.cpp的...
A.机器学习入门算法(三):基... 机器学习算法(三):K近邻(k-nearest neigh...
数字温湿度传感器DHT11模块... 模块实例https://blog.csdn.net/qq_38393591/article/deta...
有限元三角形单元的等效节点力 文章目录前言一、重新复习一下有限元三角形单元的理论1、三角形单元的形函数(Nÿ...
Redis 所有支持的数据结构... Redis 是一种开源的基于键值对存储的 NoSQL 数据库,支持多种数据结构。以下是...
win下pytorch安装—c... 安装目录一、cuda安装1.1、cuda版本选择1.2、下载安装二、cudnn安装三、pytorch...
MySQL基础-多表查询 文章目录MySQL基础-多表查询一、案例及引入1、基础概念2、笛卡尔积的理解二、多表查询的分类1、等...
keil调试专题篇 调试的前提是需要连接调试器比如STLINK。 然后点击菜单或者快捷图标均可进入调试模式。 如果前面...
MATLAB | 全网最详细网... 一篇超超超长,超超超全面网络图绘制教程,本篇基本能讲清楚所有绘制要点&#...
IHome主页 - 让你的浏览... 随着互联网的发展,人们越来越离不开浏览器了。每天上班、学习、娱乐,浏览器...
TCP 协议 一、TCP 协议概念 TCP即传输控制协议(Transmission Control ...
营业执照的经营范围有哪些 营业执照的经营范围有哪些 经营范围是指企业可以从事的生产经营与服务项目,是进行公司注册...
C++ 可变体(variant... 一、可变体(variant) 基础用法 Union的问题: 无法知道当前使用的类型是什...
血压计语音芯片,电子医疗设备声... 语音电子血压计是带有语音提示功能的电子血压计,测量前至测量结果全程语音播报࿰...
MySQL OCP888题解0... 文章目录1、原题1.1、英文原题1.2、答案2、题目解析2.1、题干解析2.2、选项解析3、知识点3...
【2023-Pytorch-检... (肆十二想说的一些话)Yolo这个系列我们已经更新了大概一年的时间,现在基本的流程也走走通了,包含数...
实战项目:保险行业用户分类 这里写目录标题1、项目介绍1.1 行业背景1.2 数据介绍2、代码实现导入数据探索数据处理列标签名异...
记录--我在前端干工地(thr... 这里给大家分享我在网上总结出来的一些知识,希望对大家有所帮助 前段时间接触了Th...
43 openEuler搭建A... 文章目录43 openEuler搭建Apache服务器-配置文件说明和管理模块43.1 配置文件说明...