C++变参模板

        从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;
}

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【最新版】ChatGPT/GPT4科研应用与AI绘图论文写作(最新增加Claude3、Gemini、Sora、GPTs技术及AI领域中的集中大模型的最新技术)

2023年随着OpenAI开发者大会的召开&#xff0c;最重磅更新当属GPTs&#xff0c;多模态API&#xff0c;未来自定义专属的GPT。微软创始人比尔盖茨称ChatGPT的出现有着重大历史意义&#xff0c;不亚于互联网和个人电脑的问世。360创始人周鸿祎认为未来各行各业如果不能搭上这班车…

关于playbook中when条件过滤报The conditional check ‘result|failed‘ failed的问题

问题现象 在使用plabook中的when做过滤脚本如下&#xff1a; --- - hosts: realserversremote_user: roottasks:- name: Check if httpd service is runningcommand: systemctl status httpdregister: resultignore_errors: True- name: Handle failed service checkdebug:ms…

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…

基于Spring Boot + Vue的电影购票系统

基于Spring Boot Vue的电影购票系统 功能介绍 分为用户端和商家端&#xff0c;商家端只能让拥有商家角色的人登录 商家可以在系统上面注册自己家的影院信息选择影院进去管理&#xff0c;在选择完要进行操作的影院后&#xff0c;可以在系统的电影库选择电影为当前的影院进行电…

Docker容器Docker桌面配置镜像加速

打开Docker Desktop应用程序&#xff0c;点击设置 具体配置如下&#xff1a; {"builder": {"gc": {"defaultKeepStorage": "20GB","enabled": true}},"experimental": false,"features": {"buil…

VScode(Python)使用ssh远程开发(Linux系统树莓派)时,配置falke8和yapf总结避坑!最详细,一步到位!

写在前面&#xff1a;在Windows系统下使用VScode时可以很舒服的使用flake8和yapf&#xff0c;但是在ssh远程开发树莓派时&#xff0c;我却用不了&#xff0c;总是出现问题。当时我就开始了漫长的探索求知之路。中间也请教过许多大佬&#xff0c;但是他们就讲“能用不就行了&…

Windows10/11配置WSL(Ubuntu)环境

文章目录 WSL介绍WSL部署扩展&#xff1a;辅助工具Windosw Terminal安装下载 WSL介绍 传统方式获取Linux操作系统&#xff0c;是安装完整的虚拟机及镜像环境&#xff0c;例如虚拟机VMware 而使用WSL,可以以非常轻量化的方式&#xff0c;得到Linux系统环境 它无需单独虚拟一套硬…

PaddlePaddle----基于paddlehub的OCR识别

Paddlehub介绍 PaddleHub是一个基于PaddlePaddle深度学习框架开发的预训练模型库和工具集&#xff0c;提供了丰富的功能和模型&#xff0c;包括但不限于以下几种&#xff1a; 1.文本相关功能&#xff1a;包括文本分类、情感分析、文本生成、文本相似度计算等预训练模型和工具。…

计算机设计大赛 行人重识别(person reid) - 机器视觉 深度学习 opencv python

文章目录 0 前言1 技术背景2 技术介绍3 重识别技术实现3.1 数据集3.2 Person REID3.2.1 算法原理3.2.2 算法流程图 4 实现效果5 部分代码6 最后 0 前言 &#x1f525; 优质竞赛项目系列&#xff0c;今天要分享的是 &#x1f6a9; 深度学习行人重识别(person reid)系统 该项目…

【C语言基础】:深入理解指针(终篇)

文章目录 深入理解指针一、函数指针变量4.1 函数指针变量的创建4.2 函数指针变量的使用4.3 typedef关键字 二、函数指针数组三、转移表四、回调函数4.1 什么是回调函数4.2 qsort使用举例4.2.1 使用qsort函数排序整形数据4.2.2 使用qsort排序结构数据4.2.3 qsort函数的模拟实现 …

WPF(1)的MVVM的数据驱动学习示例

MVVM Model:数据模型、View 界面、ViewModel 业务逻辑处理 项目结构 界面数据绑定 <Window x:Class"WpfApp1.MainWindow"xmlns"http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x"http://schemas.microsoft.com/winfx/2006/x…

Timus#1005

C【动态规划】 #include<iostream> #include<vector> using namespace std; int main() {int n;cin >> n;vector<int> dp(100000 * 20);vector<int> a(n);int ans 0, cur 0;for (int i 0; i < n; i){cin >> a[i];ans a[i];}int sum…

虚拟主播视频制作,低成本的数字人播报方案

传统的视频制作方式往往面临着成本高、周期长、人力投入大等挑战。为了满足企业对于高效、低成本视频制作的需求&#xff0c;美摄科技凭借其强大的技术研发实力&#xff0c;推出了面向企业的虚拟主播视频解决方案&#xff0c;为企业带来了全新的数字人播报视频制作体验。 美摄…

备考2025年AMC8数学竞赛:吃透2000-2024年600道AMC8真题就够

我们继续来随机看五道AMC8的真题和解析&#xff0c;根据实践经验&#xff0c;对于想了解或者加AMC8美国数学竞赛的孩子来说&#xff0c;吃透AMC8历年真题是备考最科学、最有效的方法之一。 即使不参加AMC8竞赛&#xff0c;吃透了历年真题600道和背后的知识体系&#xff0c;那么…

Linux学习——锁

目录 ​编辑 一&#xff0c;锁的概念 二&#xff0c;锁的操作 1&#xff0c;锁类型 pthread_mutex_t 2&#xff0c;初始化锁 3&#xff0c;上锁 4&#xff0c;解锁 5&#xff0c;销毁锁 三&#xff0c;线程安全问题演示 四&#xff0c;锁的原理 五&#xff0c;死锁 …

《IAB视频广告标准:综合指南(2022)》之概述篇 - 我为什么要翻译介绍美国人工智能科技公司IAB 系列(2)

IAB平台&#xff0c;使命和功能 IAB成立于1996年&#xff0c;总部位于纽约市。 作为美国的人工智能科技巨头社会媒体和营销专业平台公司&#xff0c;互动广告局&#xff08;IAB- the Interactive Advertising Bureau&#xff09;自1996年成立以来&#xff0c;先后为700多家媒体…

每日一练:LeeCode-56、合并区间【数组+滑动窗口】

4.合并区间 LeeCode-56、合并区间 以数组 intervals 表示若干个区间的集合&#xff0c;其中单个区间为 intervals[i] [starti, endi] 。请你合并所有重叠的区间&#xff0c;并返回 一个不重叠的区间数组&#xff0c;该数组需恰好覆盖输入中的所有区间 。 1 < intervals.le…

React-嵌套路由

1.概念 说明&#xff1a;在一级路由中又内嵌了其他路由&#xff0c;这种关系就叫做嵌套路由&#xff0c;嵌套至一级路由内的路由又称作二级路由。 2.实现步骤 说明&#xff1a;使用childen属性配置路由嵌套关系&#xff0c;使用<Outlet/>组件配置二级路由渲染的位置。…

重读 Java 设计模式: 解析单例模式,保证唯一实例的创建与应用

本周工作太忙了&#xff0c;变成了加班狗&#xff0c;下班回来也没时间写&#xff0c;只能利用周末时间写了&#x1f62d;。 好了&#xff0c;言归正传&#xff0c;本次我们先来介绍下设计模式中创建型模式-单例模式。 一、引言 单例模式是设计模式中最简单但又最常用的一种模…

OpenCV 绘制文字的介绍

1、前言 OpenCV 提供了用于绘制文字的putText()方法 使用这个方法不仅能够设置字体的样式、大小和颜色&#xff0c;而且能够使字体呈现斜体的效果&#xff0c;还能够控制文字的方向&#xff0c;进而使文字呈现垂直像的效果。putText()方法的语法格式如下 需要注意的是&#x…