广告
返回顶部
首页 > 资讯 > 后端开发 > 其他教程 >c++可变参数模板使用示例源码解析
  • 868
分享到

c++可变参数模板使用示例源码解析

c++可变参数模板c++可变参数 2023-01-13 18:01:08 868人浏览 薄情痞子
摘要

目录前言认识可变模板参数使用可变模板参数递归法特例化包拓展完美转发总结前言 我们知道,c++模板能力很强大,比起Java泛型这种语法糖来说,简直就是降维打击。而其中,可变参数模板,

前言

我们知道,c++模板能力很强大,比起Java泛型这种语法糖来说,简直就是降维打击。而其中,可变参数模板,就是其中一个非常重要的特性。那什么是可变参数模板,以及为什么我们需要他?

首先我们考虑一个经典的场景:

我们需要编写一个函数,来打印变量信息。

比如:

int code = 1;
string msg = "success";
printMsg(code,msg); // 输出: 1,success

而我们需要打印的参数信息是不确定的,也有可能是下面的情况:

float value = 0.8f;
printMsg(code,msg,"main"); // 输出: 1,success,main
printMsg(value,code); // 输出: 0.8,1

printMsg的参数类型、数量都是不确定的,无论是普通模板、还是使用容器,都无法完成这个任务。而可变参数模板,可以非常完美完成这个任务。

可变参数模板,意为该模板的类型与数量都是不确定,能够接收任意的参数匹配,造就了其极高的灵活度。

认识可变模板参数

template<typename T,typename... Args>
void printMsg(T t, Args... args) {}

上述代码为可变参数模板的例子。首先要了解一个概念:模板参数包,函数参数包

typename...表示一个模板参数包类型,在typename后跟了三个点 ,Args是一个模板参数包,他可以是0或多种类型的组合。Args...,表示将这个参数包展开,作为函数的形参,args也称为函数参数包

举个例子:

// T的类型是 int
// Args的类型是 int、float、string 组成的模板参数包
printMsg(1,2,0.8f,"success");
// 模板会被实例化为此函数原型
void printMsg(int,int,float,string);

对于参数包,我们可以使用sizeof... 来获取该参数包中有多少个类型。如sizeof...(args); or sizeof...(Args);

那么,对于这个可变模板参数类型,我们要如何使用它呢?

使用可变模板参数

递归法

递归法利用的是类型匹配原理,将参数包中的参数,一个个给他分离出来。我们从一个实际的例子来理解他。假如我们要实现前言章节中的printMsg函数,那么他的实现代码如下:

template<typename T,typename ...Args>
void printMsg(const T& t, const Args&... args) {
    std::cout << t << ", ";
    printMsg(args...);
}
// 调用
printMsg(1,0.3f,"success");

当我们调用printMsg(1,0.3f,"success")代码时,模板函数被实例化为:

template<int,float,string>
void printMsg(const int& t, const float& arg1, const string& arg2) {
    std::cout << t << ", ";
    printMsg(arg1, arg2); 
}

代码中再次递归调用了printMsg,模板函数被实例化为:

template<float,string>
void printMsg( const float& arg1, const string& arg2) {
    std::cout << t << ", ";
    printMsg(arg2); 
}

发现规律了吗?当我们不断递归调用printMsg时,参数报Args会被一层层解开,并将类型匹配到模板T上,从而将参数包Args中的参数逐一处理。

与此同时,我们也知道一个关键点:递归需要有终止条件。因此,我们需要在只剩下一个参数的时候将其终结:

template<typename T>
void printMsg(const T& t) {
    std::cout << t << std::endl;
}

c++在匹配模板时,会优先匹配非可变参数模板,因此非可变参数模板则成为了递归的终止条件。这样我们就实现了一个函数,能够接受任意数量、任意类型(支持<<运算符)的参数。

特例化

递归法是最为常见的使用可变参数模板的方式。对于参数包来说,除了递归法,其次就为特例化。举个例子,还是我们上面的printMsg函数:

template<>
void printMsg(const int& errorCode,const float& strength,const double& value) {
    std::cout << "errorCode:" << errorCode << " strength:" << strength << " value:" << value << std::endl;
}
printMsg(1,0.8f,0.8);

针对<int,float,double>类型的模板做了一个特例化,则在我们调用此类型的模板时,会优先匹配特例化。这也是一种处理可变模板参数的方式。

除此之外,还有很多对于可变模板参数的神奇用法,进一步提高他的灵活性。

包拓展

这里包,指的是函数参数包以及可变模板参数包。前面的例子中已经存在两个包拓展,但更多的是属于可变参数模板的语法层面,所以并没有展开说。比如上面我们提到的代码:

template<typename T,typename ...Args>
void printMsg(const T& t, const Args&... args) {
    std::cout << t << ", ";
    printMsg(args...);
}
printMsg(1,0.8f,0.8);

这里有两个包拓展:

  • 函数的形参,在Args& 之后跟了三个点,表示将Args参数包展开,例子中展开后的函数原型是void printMsg(const int&,const float&,const double&);
  • 第二处展开是在递归调用时,将函数参数包形参展开args...,例子中展开后为printMsg(0.8f,0.8);

在涉及到函数调用、函数声明时,都需要用到上面这两个包拓展语法。但我们会发现并没有什么可以操作的空间,他更多就是一个可变模板函数的固定语法。但除此之外,包拓展可以有一个更加神奇的操作。

还是上面的例子,但是这里我们需要对打印的数据进行一轮过滤,对int数据超过99、float数据超过0.9进行预警报告,其他数据不做处理。那么这个怎么处理呢?

理论上说,我们需要对每个参数包中的每个数据进行处理,那我们可以在递归中,判断T的类型,再根据不同的类型进行处理。这种方式是可行的,但c++提供了更加好用的另一种方式。看下面的代码:

template<typename T>
const T& filterParam(const T& t) { return t; }
template<>
const int& fileterParam(const int& t) {
    if (t > 99) { onWarnReport(); }
    return t;
}
template<>
const float& fileterParam(const float& t) {
    if (float > 0.9) { onWarnReport(); }
    return t;
}
template<typename... Args>
void printMsgPlug(const Args&... args) {
    printMsg(filterParam(args)...);  //关键代码
}
printMsgPlus(1,0,3f,1.8f);

可以看到我们的关键代码在于printMsg(filterParam(args)...);这一行,他等价于printMsg(filterParam(1),filterParam(0.3f) ,filterParam(1.8f)); 三个小点移动到了函数调用的后面,即可以实现这样的效果。

这种方式的优点在于,他可以将过滤相关的逻辑,抽离到另外一个函数中去单独处理,利用模板的特性对数据进行统一或者单独处理。而且,使用typeId判断类型的方式并不总是可靠的,这种方式会更加稳定。

此外,针对双重过滤的方式,包拓展的解决方案也会更加优雅。假如,我们在打印数据之前,需要对数据进行一次转换,之后再对转换结果进行过滤判断是否需要预警报告。那么我们的伪代码可以是如下:

template<typename T>
T filterParam(const T& t) {
    T result = convertParam(t);
    if()...
    return result;
}
template<typename T>
T convertParam(const T& t) {...}
template<typename... Args>
void printMsgPlug(const Args&... args) {
    printMsg(filterParam(args)...);  //关键代码
}

而如果使用递归结合typeid的方式,可能就需要更多个switch进行类型匹配嵌套解决,且其结果总是不可靠的。

最后,并不是所有可变模板函数,都能使用递归去解决问题。例如我们需要一个能够构建unique_ptr的函数,他的简化版可以是这样的:

template<typename T, typename... Args>
std::unique_ptr<T> make_unique(Args&... args) {
    return std::unique_ptr<T>(new T(fileterParam(args)...));
}

这个写法是不够完善的,但是方便我们理解。这个时候,如果我们需要对参数进行过滤,那么递归的方式,就无法在这里使用了,而必须使用包拓展。

完美转发

完美转发在可变模板中非常常见,他的作用在于保持原始的数据类型。参考我们上面的make_unique函数,在移除fileterParam函数之后,,我们希望,传给make_unique函数的数据,能够原封不动地,传递给T的构造函数。那么他的实现如下:

template<typename T, typename... Args>
std::unique_ptr<T> make_unique(Args&&... args) {
    return std::unique_ptr<T>(new T(std::forward<Args>(args)...));
}
  • Args&& 表示通用引用,他能接收左值引用,也可以接收右值引用。
  • std::forward 表示保持参数的原始类型。因为我们知道,右值引用本身是左值,所以我们需要将其转为右值传递给构造函数。

这样,我们就能够原封不动地将数据传递给构造函数,而不修改数据类型。这部分类型属于右值与引用的范畴,这里不详细展开解析。

但是对于可变模板来说,这里有一个关键需要注意一下:通用引用的本身,是 引用类型。假如我们传递了一个int类型进来,那么转化之后就变成了int&。此时如果我们使用Args类型去做模板匹配,很容易发生匹配失败的问题,会提示int&无法匹配到int类型,需要多加注意一下。要解决这个问题也比较简单,将其引用类型移除即可。在c++11中,可以使用以下代码移除所有的修饰与引用,保持基础的数据类型:

template<typename T>
using remove_cvRef = typename std::remove_cv<typename std::remove_reference<T>::type>::type;
std::vector<decltype(remove_cvRef<T>)> v;

在匹配模板的时候,可以使用decltype来获取移除后的类型进行匹配。

总结

可变参数模板在实际的使用中,更多还是结合完美转发来使用,实现对象的统一构造或者接口调用封装等。可变参数的存在,使得模板接口的灵活度提升了一个档次,如果你在实际开发中遇到类似的需求,不妨使用一下,会给你带来惊喜的。

以上就是c++可变参数模板使用示例源码解析的详细内容,更多关于c++可变参数模板的资料请关注编程网其它相关文章!

--结束END--

本文标题: c++可变参数模板使用示例源码解析

本文链接: https://www.lsjlt.com/news/177691.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

本篇文章演示代码以及资料文档资料下载

下载Word文档到电脑,方便收藏和打印~

下载Word文档
猜你喜欢
  • c++可变参数模板使用示例源码解析
    目录前言认识可变模板参数使用可变模板参数递归法特例化包拓展完美转发总结前言 我们知道,C++模板能力很强大,比起Java泛型这种语法糖来说,简直就是降维打击。而其中,可变参数模板,...
    99+
    2023-01-13
    c++可变参数模板 c++可变参数
  • C++11可变参数模板的参数转发举例分析
    本篇内容主要讲解“C++11可变参数模板的参数转发举例分析”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“C++11可变参数模板的参数转发举例分析”吧!实例很多软件系统都存在日志(log)功能,通...
    99+
    2023-06-19
  • C++11可变参数模板怎么使用
    本篇内容主要讲解“C++11可变参数模板怎么使用”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“C++11可变参数模板怎么使用”吧!可变参数函数C语言中,可变参数函数可以说是一个比较神奇的存在。例...
    99+
    2023-06-19
  • C++新标准难点解析之什么是可变模板参数
    本篇内容介绍了“C++新标准难点解析之什么是可变模板参数”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成! 前言C++的新特性--可变...
    99+
    2023-06-15
  • 2022数模国赛C题思路解析(可供训练用 源码可供参考)
    博主和两位队友参加了此次比赛,仅以此篇博客聊表纪念,并且最后也获得了不错的成绩 希望对大家有所帮助 持续更新~~ 关于数据集和完整代码可以关注点赞收藏后评论区留下QQ邮箱或者私信博主要 有疑问可以评论区留言 摘要 古代玻璃制品是中...
    99+
    2023-09-01
    算法 机器学习 人工智能 python matplotlib
  • C++11中的可变参数模板和lambda表达式怎么使用
    本篇内容介绍了“C++11中的可变参数模板和lambda表达式怎么使用”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!1.可变参数模板C++1...
    99+
    2023-07-05
  • 使用Apache commons-cli包进行命令行参数解析的示例代码
    Apache的commons-cli包是专门用于解析命令行参数格式的包。  依赖: <dependency> <groupId>commons-cli</groupId&g...
    99+
    2022-06-04
    apache commons cli commons cli
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作