C++变参模板

news2024/9/28 5:33:56

        从c++11开始,模板可以接受一组数量可变的参数,这种技术称为变参模板。

变参模板

        下面一个例子,通过变参模板打印一组数量和类型都不确定的参数。

#include <iostream>
#include <string>

void print(void)
{
    std::cout<<"........................"<<std::endl;
}

template <typename T, typename ... Ts>
void print(T arg1, Ts ... args)
{
    std::cout<<arg1<<std::endl;
    print(args ...);
}

int main(int argc, char **argv)
{
    print("hello", 7.5, 10, std::string("building"));
}

        看到上面这段代码,首先会产生两个疑问:

  • void print(T arg1, Ts ... args)中arg1是什么作用?
  • void print(void)有什么作用?

        下面将通过解释这段代码的运行过程,来解答上面的问题。仔细观察上面这段代码,不难发现,print函数模板是一个递归函数模板。执行过程大体如下:

  1. main函数调用print("hello", 7.5, 10, std::string("building"));,"hello"赋值给arg1,其余参数赋值给args
  2. print函数输出"hello",再次调用自身print(7.5, 10, std::string("building"));,7.5赋值给arg1,其余参数赋值给args
  3. print函数输出7.5,再次调用自身print(10, std::string("building"));,10赋值给arg1,其余参数赋值给args
  4. print函数输出10,再次调用自身print(std::string("building"));,"building"赋值给arg1,args为空
  5. print函数输出"building",因为args为空,此时不在调用自身,而是重载函数print(void),然后结束递归。

        从整个过程来看,arg1的主要作用就是从args迭代取值,print(void)负责处理args为空的情况。那么不定义void print(void)是否可以呢?答案是否定的,不定义该函数,编译将会报错“No matching function for call to 'print'”。

        此处还应该注意一个问题,print和c/c++的printf原理不一样:printf通过va_list实现变参,而print函数模板是为每种情况都生成了一个重载函数,如下:

        上面的信息来自于xcode调试,当然,也可以通过objdump查看,也会得到相同的结果,编译器确实生成了多个print重载函数:

         当然,上面的代码还可以写成下面的样子:

template <typename T>
void print(T arg)
{
    std::cout<<arg<<std::endl;
}

template <typename T, typename ... Ts>
void print(T arg1, Ts ... args)
{
    print(arg1);
    print(args ...);
}

        如果代码中没有print(arg1),程序知会打印最后一个参数building,print只有迭代到最后一个参数时,才会找到合适的函数print(T arg)。

        但一定要注意,下面的实现方式是错误的,无递归结束条件,无限迭代,直到耗尽堆栈空间:

void print(void)
{
}

template <typename ... Ts>
void print(Ts ... args)
{
    print(args ...);
}

折叠表达式

        从c++17开始,c++引入了一种更为简洁灵活的编程方式——折叠表达式,下面是一个简单的例子:

#include <cstdio>

template <typename ...T>
auto sum(T ... args)
{
    return (... + args);
}

int main(int argc, char **argv)
{
    int s = sum(1, 2, 3, 4, 5);
    printf("%d\n", s);
}

        几乎所有的二元运算符都可以用于折叠表达式,下面是一些其他运算符的例子:

template <typename F, typename ...T>
auto apply(F f, T ...args)
{
    return (f(args), ...);
}

template <typename ...T>
bool and_op(T ...args)
{
    return (args && ...);
}

        迭代表达式,仅仅是围绕一个操作符简单地展开,例如连加。因此,对于三元操作符:?,很难用迭代表达式来实现。所以,想使用迭代表达式和:?求一个集合中的极值,是无法实现的。但是可以通过其他方式实现,下面便是一种实现方式:

template <typename T>
struct min_op final
{
public:
    min_op(T data) : is_first(true), min_data(data) {
        
    }
    
    T operator()(T rhs) {
        if (is_first) {
            is_first = false;
            min_data = rhs;
            return min_data;
        }
        
        min_data = min_data < rhs ? min_data : rhs;
        return min_data;
    }
    
private:
    bool is_first;
    T min_data;
    
};

template <typename T, typename ...Ts>
auto min(T arg, Ts ... args)
{
    min_op<T> op(arg);
    return (op(args), ...);
}

        很明显,这种方式还不如直接使用for循环直接利索。

        与之前的递归迭代方式相比,迭代表达式最大的优点是编译器没有为其生成过多的重载函数。迭代表达式与之前的优点:不使用vector,不会生成多个函数,缺点:解决元素较少的情况。

        变参模板的优点:

  • 可以支持不同的类型
  • 可以不使用容器
  • 直接访问元素,效率比较高

        但其并不是完美无缺的,:

  • 不适用元素较多的情况
  • 使用递归迭代会生成大量的重载函数

变参类模板和变参表达式

变参表达式

        函数参数包除了转发所有参数外,还可以做其他事,例如计算他们的值。

template <typename ... Ts>
void print_doubled(Ts ... args)
{
    print((args + args) ...);
}

...
print_doubled(1, 2, 3, 4, 5, 6);
...

变参下标

        作为另外一个例子,下面的函数通过一组变参下标来访问第一个参数中相应的元素:

template<typename T, typename ...IDS>
void print_elems(T a, IDS ...ids)
{
    print(a[ids]...);
}

...
std::vector<int> v{1, 2, 3, 4, 5, 6};
print_elems(v, 1, 3, 5);
...

变参模板类

        提到变参模板类,首先会想到std::tuple,该种技术使得不定义新类型的前提下,多值返回成为一种可能,提供了更加灵活的编程方式,例如:

template <typename T>
std::tuple<T, T, T,  T> calc(T x, T y)
{
    return std::make_tuple(x + y, x - y, x * y, x / y);
}

...
auto result = calc(10.0, 2.5);
...

 变参基类

        变参基类从不定数的基类派生出一个新的类,主要目的是代码复用,比普通写法更加方便,派生类无需引入基类头文件,但需要注意多继承陷阱。下面是一个简单的例子:

#include <cstdio>

struct fly_animal
{
    void fly(void) { printf("flying !\n"); }
};

struct swim_animal
{
    void swim(void) { printf("swiming !\n"); }
};

struct run_animal
{
    void run(void) { printf("running !\n"); }
};

struct fish 
{
    //...
};

struct bird 
{
    //...
};

struct mammal
{
    //....
};


template <typename ...Bases>
struct overloader : Bases...
{
    //using Bases::operator()...;
};


int main(int argc, const char **argv)
{
    using flyfish = overloader<fly_animal, swim_animal>;
    flyfish ff;
    ff.fly();
    ff.swim();
    
    using crocodile = overloader<run_animal, swim_animal>;
    crocodile ccdl;
    ccdl.run();
    ccdl.swim();
    
    using cat = overloader<mammal, run_animal>;
    cat ct;
    ct.run();
    
    return 0;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1506915.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

数据结构小记【Python/C++版】——散列表篇

一&#xff0c;基础概念 散列表&#xff0c;英文名是hash table&#xff0c;又叫哈希表。 散列表通常使用顺序表来存储集合元素&#xff0c;集合元素以一种很分散的分布方式存储在顺序表中。 散列表是一个键值对(key-item)的组合&#xff0c;由键(key)和元素值(item)组成。键…

探索云原生数据库技术:构建高效可靠的云原生应用

数据库是应用开发中非常重要的组成部分&#xff0c;可以进行数据的存储和管理。随着企业业务向数字化、在线化和智能化的演进过程中&#xff0c;面对指数级递增的海量存储需求和挑战以及业务带来的更多的热点事件、突发流量的挑战&#xff0c;传统的数据库已经很难满足和响应快…

OpenCV filter2D函数详解

OpenCV filter2D函数简介 OpenCV filter2D将图像与内核进行卷积&#xff0c;将任意线性滤波器应用于图像。支持就地操作。当孔径部分位于图像之外时&#xff0c;该函数根据指定的边界模式插值异常像素值。 该函数实际上计算相关性&#xff0c;而不是卷积&#xff1a; filter…

【Spark编程基础】实验一Spark编程初级实践(附源代码)

文章目录 一、实验目的二、实验平台三、实验内容和要求1. 计算级数2. 模拟图形绘制3.统计学生成绩 一、实验目的 1.掌握 Scala 语言的基本语法、数据结构和控制结构&#xff1b; 2.掌握面向对象编程的基础知识&#xff0c;能够编写自定义类和特质&#xff1b; 3.掌握函数式编程…

vue3速查笔记

文章目录 一、创建Vue3.0工程1.使用 vue-cli 创建2.使用 vite 创建 二、常用 Composition API1.拉开序幕的setup2.ref函数3.reactive函数4.Vue3.0中的响应式原理vue2.x的响应式Vue3.0的响应式 5.reactive对比ref6.setup的两个注意点7.计算属性与监视1.computed函数2.watch函数3…

Windows电脑安装Linux(Ubuntu 22.04)系统(图文并茂)

Windows电脑安装Ubuntu 22.04系统&#xff0c;其它版本的Ubuntu安装方法相同 Ubuntu 16.04、Ubuntu 18.04安装方法相同&#xff0c;制作U盘启动项的镜像文件下载你需要的版本即可&#xff01; Ubuntu的中文官网网址&#xff1a;https://cn.ubuntu.com/&#xff0c;聪明的你一定…

【线程】封装 | 安全 | 互斥

线程封装&#xff08;面向对象&#xff09; 1.组件式的封装出一个线程类&#xff08;像C11线程库那样去管理线程&#xff09; 我们并不想暴露出线程创建&#xff0c;终止&#xff0c;等待&#xff0c;分离&#xff0c;获取线程id等POSIX线程库的接口&#xff0c;我们也想像C1…

IDEA管理Git + Gitee 常用操作

文章目录 IDEA管理Git Gitee 常用操作1.Gitee创建代码仓库1.创建仓库1.点击新建仓库2.完成仓库信息填写3.创建成功4.管理菜单可以修改这个项目的设置 2.设置SSH公钥免密登录基本介绍1.找到.ssh目录2.执行指令 ssh-keygen3.将公钥信息添加到码云账户1.点击设置2.ssh公钥3.复制.…

React-配置别名@

1.概念 说明&#xff1a;路径解析配置(webpack),把/解析为src/&#xff1b;路径联想配置(VsCode),VsCode在输入/时&#xff0c;自动联想出来对应的src/下的子级目录。CRA本身把webpacki配置包装到了黑盒里无法直接修改&#xff0c;需要借助一个插件-craco。 2.实现步骤 2.1安…

docker常用操作-docker私有仓库的搭建(Harbor),并将本地镜像推送至远程仓库中。

1、docker-compose安装&#xff0c;下载docker-compose的最新版本 第一步&#xff1a;创建docker-compose空白存放文件vi /usr/local/bin/docker-compose 第二步&#xff1a;使用curl命令在线下载&#xff0c;并制定写入路径 curl -L "https://github.com/docker/compos…

npm市场发布包步骤

1.打开npm官网npm官网 2.创建自己的账号 3.查看当前npm的镜像源&#xff0c; 如果出现淘宝的镜像源则需要切换成官方的镜像源 npm config get registry //查看镜像源 https://registry.npm.taobao.org/ //淘宝的镜像源 https://registry.npmjs.org/ //官方的镜像源 …

IPO[困难]

优质博文IT-BLOG-CN 一、题目 假设你的公司即将开始IPO。为了以更高的价格将股票卖给风险投资公司&#xff0c;你的公司希望在IPO之前开展一些项目以增加其资本。 由于资源有限&#xff0c;它只能在IPO之前完成最多k个不同的项目。帮助你的公司设计完成最多k个不同项目后得到最…

基于遗传算法GA的机器人栅格地图最短路径规划,可以自定义地图及起始点(提供MATLAB代码)

一、原理介绍 遗传算法是一种基于生物进化原理的优化算法&#xff0c;常用于求解复杂问题。在机器人栅格地图最短路径规划中&#xff0c;遗传算法可以用来寻找最优路径。 遗传算法的求解过程包括以下几个步骤&#xff1a; 1. 初始化种群&#xff1a;随机生成一组初始解&…

LC3014 输入单词需要的最少按键次数Ⅰ与方法内容的易读性

题目 刷题做到力扣 3014&#xff0c;题目要求设计电话键盘上的按键映射&#xff0c;返回按出 word 单词的最小按键次数&#xff0c;1 ≤ word.length ≤ 26&#xff0c;且仅由小写英文字母组成&#xff0c;所有字母互不相同 我的题解 简单题&#xff0c;略加思索拿下&#x…

给定l,r(1e18),定义f(x):x中最大的数位减去最小数位。对于l<=x<=r, 求f(x)最小值

题目 #include <bits/stdc.h> using namespace std; #define int long long #define pb push_back #define fi first #define se second #define lson p << 1 #define rson p << 1 | 1 const int maxn 1e6 5, inf 1e18, maxm 4e4 5, base 397; const i…

【JAVA】CSS3伸缩盒案例、响应式布局、BFC

1.CSS3伸缩盒案例 效果&#xff1a;用伸缩盒模型 <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>Document</title>&…

Day33:安全开发-JavaEE应用SQL预编译Filter过滤器Listener监听器访问控制

目录 JavaEE-预编译-SQL JavaEE-过滤器-Filter JavaEE-监听器-Listen 思维导图 Java知识点 功能&#xff1a;数据库操作&#xff0c;文件操作&#xff0c;序列化数据&#xff0c;身份验证&#xff0c;框架开发&#xff0c;第三方库使用等. 框架库&#xff1a;MyBatis&#…

复合查询【MySQL】

文章目录 复合查询测试表 单表查询多表查询子查询单行子查询多行子查询IN 关键字ALL 关键字ANY 关键字 多列子查询 合并查询 复合查询 测试表 雇员信息表中包含三张表&#xff0c;分别是员工表&#xff08;emp&#xff09;、部门表&#xff08;dept&#xff09;和工资等级表&…

Python刘诗诗

写在前面 刘诗诗在电视剧《一念关山》中饰演了女主角任如意&#xff0c;这是一个极具魅力的女性角色&#xff0c;她既是一位有着高超武艺和智慧的女侠士&#xff0c;也曾经是安国朱衣卫前左使&#xff0c;身怀绝技且性格坚韧不屈。剧中&#xff0c;任如意因不满于朱衣卫的暴行…

Java多线程实战-实现多线程文件下载,支持断点续传、日志记录等功能

&#x1f3f7;️个人主页&#xff1a;牵着猫散步的鼠鼠 &#x1f3f7;️系列专栏&#xff1a;Java全栈-专栏 &#x1f3f7;️个人学习笔记&#xff0c;若有缺误&#xff0c;欢迎评论区指正 目录 前言 1 基础知识回顾 1.1 线程的创建和启动 1.2 线程池的使用 2.运行环境说…