C++展开模板参数包、函数参数包-(lambda+折叠表达式)

news2024/10/2 16:20:52

开门见山

以下代码可展开模板参数包和展开函数参数包。

// lambda+折叠表达式(需C++17)
#include <iostream>
using namespace std;

// 1.展开模板参数包
template<typename ...T>
void Func1()
{
    ([]() {
        cout << typeid(T).name() << endl;
     }(), ...);// 折叠表达式
}
// 2.展开函数参数包
template<typename ...T>
void Func2(T... args)
{
    ([&args]() {
        cout << args << endl;
    }(), ...);// 折叠表达式
}
void main(){
    cout << "开门见山" << endl;
    cout << "1.展开模板参数包" << endl;
    Func1<char, bool, int, char, const char*>();
    cout << endl;
    cout << "2.展开函数参数包" << endl;
    Func2('c', true, 2, 'h', "CSDN越来越好。。。");
}

请添加图片描述

文章目录

  • 开门见山
  • 前言
    • 提前声明
    • 遇到的问题
  • 一步一步理解过程
    • 1.1 模板推断类型
    • 1.2 参数包转发、递归解函数包
    • 1.3 不用递归解包-外部函数-折叠表达式
    • 1.4 不用递归解包-lambda函数-折叠表达式
    • 1.5 lambda+折叠表达式解开模板参数包
    • 1.6 回归组件代码

前言

提前声明

  • 关于

    我菜鸟一枚,文中的术语、代码、文字若有错误,欢迎指正

  • 阅读本文需要了解的知识

    模板、模板参数包、函数参数包、参数包转发、扩展参数包、折叠表达式、lambda函数。

  • 本文目的

    是记录自己遇到的C++语法问题如何运用所学知识慢慢理解的过程,不属于学习语法文章。

    相关不理解知识点建议百度。

遇到的问题

  • 需求说明

    • 在Unity的游戏引擎中,Ctrl+D复制一个物体,新物体应该具有旧物体的各个组件。

    • 组件列表有:TransformComponent、SpriteRendererComponent、CircleRendererComponent。

    • 如果要复制这三个组件给新物体,假设Unity内部实现代码如下

    template<typename Component>
    static void CopyComponentIfExists(Entity dst, Entity src) {
        if (src.HasComponent<Component>()) {
            // 新物体添加旧物体组件
            dst.AddOrReplaceComponent<Component>(src.GetComponent<Component>());
        }
    }
    void Scene::DuplicateEntity(Entity entity)
    {
        // 1.创建旧实体同名的新实体
        std::string name = entity.GetName();
        Entity newEntity = CreateEntity(name);
        // 2.复制组件
        CopyComponentIfExists<TransformComponent>(newEntity, entity);
        CopyComponentIfExists<SpriteRendererComponent>(newEntity, entity);
        CopyComponentIfExists<CircleRendererComponent>(newEntity, entity);
    }
    
    
  • 简化代码

    若组件列表有20个,那么CopyComponentIfExists这行代码需要20个,显然代码会冗余,应使用模板来简化此代码,代码如下

    template<typename... Component>
    struct ComponentGroup {
    };
    using AllComponents = ComponentGroup<TransformComponent, SpriteRendererComponent,
            CircleRendererComponent>;
    
    template<typename... Component>
    static void CopyComponentIfExists(Entity dst, Entity src) {
        ([&]() {
            if (src.HasComponent<Component>()) {
                // 新物体添加旧物体组件
                dst.AddOrReplaceComponent<Component>(src.GetComponent<Component>());
            }
        }(), ...);
    }    
    template<typename... Component>
    static void CopyComponentIfExists(ComponentGroup<Component...>, Entity dst, Entity src) {
        CopyComponentIfExists<Component...>(dst, src);
    }
    void Scene::DuplicateEntity(Entity entity)
    {
        // 1.创建旧实体同名的新实体
        std::string name = entity.GetName();
        Entity newEntity = CreateEntity(name);
        // 2.复制组件
        CopyComponentIfExists(AllComponents{}, newEntity, entity);
        //CopyComponentIfExists<TransformComponent>(newEntity, entity);
        //CopyComponentIfExists<SpriteRendererComponent>(newEntity, entity);
        //CopyComponentIfExists<CircleRendererComponent>(newEntity, entity);
    }
    
    • 模板代码完成新物体拷贝旧物体组件的任务。

    • 假设有20个组件类型,只要在AllComponents写上这20个组件,再调用CopyComponentIfExists(AllComponents{}, newEntity, entity);就不用像之前写20行代码。

    • 但是缺点也产生了,就是代码变得很难理解了。

  • 简化核心代码( tips:这里被下面过程称为 组件代码

    我看到上一小点的代码,不知模板如何运作的,于是写下以下简洁版代码,只要理解了以下代码等同于理解上一小点的代码

    #include <iostream>
    using namespace std;
    
    struct TransformComponent {
    	TransformComponent() { cout << "TransformComponent()" << endl; }
    };
    struct SpriteRendererComponent {
    	SpriteRendererComponent() { cout << "SpriteRendererComponent()" << endl; }
    };
    struct CircleRendererComponent {
    	CircleRendererComponent() { cout << "CircleRendererComponent()" << endl; }
    };
    
    template<typename... Component>
    struct ComponentGroup {
    	ComponentGroup() { cout << "ComponentGroup()" << endl; }
    };
    using AllComponents = ComponentGroup<TransformComponent, SpriteRendererComponent,CircleRendererComponent>;
    
    // 为复制实体的辅助方法
    template<typename... Component>
    static void CopyComponentIfExists() {
        ([]() {
            cout  << typeid(Component).name() << endl;
        }(), ...);
    }
    template<typename... Component>
    static void CopyComponentIfExists(ComponentGroup<Component...>) {
        CopyComponentIfExists<Component...>();
    }
    void main() {
    	CopyComponentIfExists(AllComponents{});
    }
    

    这简化核心版的代码(别称:组件代码)最重要的就是理解:如何展开模板参数包

    由于在网上搜索相关模板参数知识点,都是讲如何展开函数参数包的,没有讲如何展开模板参数包(也许我自己菜,没有搜到),所以无奈,在自己花了一下午的时间,自己慢慢试出来了,编码理解过程如下。

一步一步理解过程

1.1 模板推断类型

  • 参考例子1

    #include <iostream>
    #include <string>
    using namespace std;
    
    template<typename T>
    static void Func1(T t) {
        T t2 = 8;
        cout << t << " " << t2 << endl;
    }
    template<typename T>
    static void Func2() {
        T t = 7;
        cout << t << endl;
    }
    void main() {
        Func1<int>(3);
        Func1(4);// 这就是省略了声明模板类型,由参数4推断Func1的模板T类型为int
        Func2<int>();
    }
    
  • 所以可以理解以下代码

    template<typename... Component>
    struct ComponentGroup {
    	ComponentGroup() { cout << "ComponentGroup()" << endl; }
    };
    using AllComponents = ComponentGroup<TransformComponent,SpriteRendererComponent,CircleRendererComponent>;
    
    template<typename... Component>
    static void CopyComponentIfExists(ComponentGroup<Component...>) {
        // 传给CopyComponentIfExists的模板参数包,为当前函数参数包推断出来的模板参数包
        CopyComponentIfExists<Component...>();
    }
    void main() {
        // 传入AllComponents{}代表传入实参,CopyComponentIfExists的模板类型从实参推断出来
    	CopyComponentIfExists(AllComponents{});
    }
    

    在CopyComponentIfExists函数内

    1. ComponentGroup<>,是带有模板参数包的struct

    2. ComponentGroup<Component…>,

      Component…模板参数包由函数参数包AllComponents{}推断出来,像上面例子1的Func1(4);

      由于AllComponents = ComponentGroup<TransformComponent,SpriteRendererComponent,CircleRendererComponent>

      传入的函数参数包AllComponents{}

      则能推断出模板参数包
      Component… = TransformComponent, SpriteRendererComponent,CircleRendererComponent

    3. 得到了Component…模板参数包后,再当做显示的模板传给下个函数

      代码CopyComponentIfExists<Component…>();
      相当于
      CopyComponentIfExists<TransformComponent, SpriteRendererComponent,CircleRendererComponent>();

1.2 参数包转发、递归解函数包

  • 此小节为理解

    • 参数包转发
    • 解函数包概念
  • 则给出以下代码

    例子2:显示指定模板参数包类型、并传入函数参数包(与上有点不同,为理解解包概念)、并递归解函数包

    #include <iostream>
    using namespace std;
    
    // 2.递归解函数包
    void Func2() { cout << "Func2(),因为解包完了,无参数,就调用此函数,代表递归解包结束" << endl; }// 递归终止函数
    
    template<typename T, typename ...TT>
    void Func2(T& val, TT... args)                          // 函数参数包的第一个赋给第一个参数,剩下的都给参数包args
    {
        cout << val << "-->" << typeid(val).name() << endl;// 打印获取当前参数包的第一个参数值和类型
        
        // 继续解包,将函数参数包传给本函数递归,不指定模板参数包类型,由函数参数包推断出来
        Func2(args...);                                     
    }
    // 1.参数包转发
    template<typename... T>
    void Func1(T... args) {
        /*
            传给Func2函数的模板参数包,为当前函数参数包推断出来的模板参数包,并将多个实参传入
            与CopyComponentIfExists<Component...>();不同,这里有传递实参
        */
        //Func2<T...>(args...); // 转发模板参数包,函数参数包
        Func2<int, int, char, char const*>(args...);// 与上一段代码调用一样,只不过显示指定模板参数包类型
    }
    void main() {
        cout << "参数包转发、递归解包"<< endl;
        Func1(2, 3, 'c', "12312");
    }
    

    请添加图片描述

1.3 不用递归解包-外部函数-折叠表达式

  • 引入

    由1.2的例子解包,知道了解包概念和流程,但是为靠近一开始的组件代码,不使用递归而实现的解包代码如下

    #include <iostream>
    using namespace std;
    
    // 2.不用递归解包
    template <class T>
    void Func2(T val)
    {
        cout << val << "-->" << typeid(val).name() << endl;
    }
    template<typename ...T>
    void Func2(T... args)                          
    {
        /* 
        重点在这:(func(args), ...);
            意思是逐个展开args函数参数包,并将解开的一个参数传入Func2,有多少个参数就有多少个Func2的调用。
            可以理解展开的语句为:Func2(2), Func2(3), Func2('c'), Func2("12312");
        */
        (Func2(args), ...);// 折叠表达式解包
    }
    // 1.参数包转发
    template<typename... T>
    void Func1(T... args) {
        /*
            传给Func2函数的模板参数包,为当前函数参数包推断出来的模板参数包,并将多个实参传入
            与CopyComponentIfExists<Component...>();不同,这里有传递函数参数包
        */
        Func2<T...>(args...); 
        //Func2<int, int, char, char const*>(args...);// 与上一段代码调用一样
    }
    void main() {
        cout << "不用递归解包-外部函数"<< endl;
        // 相当于Func2(2, 3, 'c', "12312");但为了与前一致讲述参数包转发,所以还是Func1
        Func1(2, 3, 'c', "12312"); 
    }
    

    请添加图片描述

1.4 不用递归解包-lambda函数-折叠表达式

由1.3例子的重点那段注释,Func2(args)可以改写成lambda匿名函数

#include <iostream>
using namespace std;

// 2.不用递归解包-并用lamda
template<typename ...T>
void Func2(T... args)
{
  /*
  重点在这:([&](){}(), ...);
      意思是逐个展开args函数参数包,并将解开的一个参数被lambda捕获,有多少个参数就有多少个lambda的调用。
      可以理解展开的语句为:
      args = 2;       [&]() {cout << args << "-->" << typeid(args).name() << endl; }(); 
      args = 3;       [&]() {cout << args << "-->" << typeid(args).name() << endl; }(); 
      args = 'c';     [&]() {cout << args << "-->" << typeid(args).name() << endl; }(); 
      args = "12312"; [&]() {cout << args << "-->" << typeid(args).name() << endl; }();
      实际上args函数参数包被展开的新变量名为:args_0、args_1、args_2、args_3
  */
  // & 隐式引用捕获的是解开args函数参数包,得到一个参数赋给args的变量
  ([&]() {
      cout << args << "-->" << typeid(args).name() << endl;
  }(), ...);// 折叠表达式解包
}
// 1.参数包转发
template<typename... T>
void Func1(T... args) {
  Func2<T...>(args...);
}
void main() {
  cout << "不用递归解包-lambda" << endl;
  Func1(2, 3, 'c', "12312");
}

请添加图片描述

1.5 lambda+折叠表达式解开模板参数包

由1.4的例子发现与原组件代码很接近了,但是原组件代码并没有函数参数包传递,只有推断出来的模板参数包传递。

于是问题再于是否能像1.4代码用lamda解函数参数包那样解开模板参数包呢?答案是肯定的,如下

#include <iostream>
using namespace std;

// 2.用lamda解开模板参数包
template<typename ...T>
void Func2()
{
    // & 隐式引用捕获的是解开模型参数包,得到一个类型赋给T的变量
    // 但是不写&也行,1.4节要写也许args是参数在函数体内,而这里T是类型且在函数体外所以不用?
    ([]() {
        cout << typeid(T).name() << endl;
     }(), ...);// 折叠表达式
}
// 1.模板参数包转发
template<typename... T>
void Func1(T... args) {
    // 模板参数包的由参数包推断出来
    // <T...> = <int, int, char, const char*>,转发模板参数包
    Func2<T...>();
}
void main() {
    cout << "类似组件(代码)-模板参数包转发-lambda解包" << endl;
    Func1(2, 3, 'c', "12312");
}

请添加图片描述

1.6 回归组件代码

由以上的步骤,不难理解原本的组件代码意思和流程了

  1. 传入AllComponents{}代表传入实参
  2. 第一个函数的模板参数包由函数参数包推断出来
  3. 第一个函数推断出来的模板参数包转发,传递给第二个函数
  4. 第二个函数接收到传过来的模板参数包
  5. 折叠表达式解开模板参数包,并用lambda输出
#include <iostream>
using namespace std;

struct TransformComponent {
	TransformComponent() { cout << "TransformComponent()" << endl; }
};
struct SpriteRendererComponent {
	SpriteRendererComponent() { cout << "SpriteRendererComponent()" << endl; }
};
struct CircleRendererComponent {
	CircleRendererComponent() { cout << "CircleRendererComponent()" << endl; }
};

template<typename... Component>
struct ComponentGroup {
	ComponentGroup() { cout << "ComponentGroup()" << endl; }
};
using AllComponents = ComponentGroup<TransformComponent, SpriteRendererComponent,CircleRendererComponent>;

// 4.接收到传过来的模板参数包
// <Component...> = <TransformComponent, SpriteRendererComponent,CircleRendererComponent>
template<typename... Component>
static void CopyComponentIfExists() {
    // 5.解开模板参数包,并用lambda输出
    ([]() {
        cout  << typeid(Component).name() << endl;
    }(), ...);// (, ...)折叠表达式解包
}
// 2.模板参数包由函数参数包推断出来
template<typename... Component>
static void CopyComponentIfExists(ComponentGroup<Component...>) {
	// 3.推断出来的模板参数包转发,传递
    CopyComponentIfExists<Component...>();
    // <Component...> = <TransformComponent, SpriteRendererComponent,CircleRendererComponent>
}
void main() {
    cout << "1.6 回归组件代码"<<endl;
	CopyComponentIfExists(AllComponents{});// 1.传入AllComponents{}代表传入实参
/*
由于:
	AllComponents{} = ComponentGroup<TransformComponent, SpriteRendererComponent,CircleRendererComponent>()
所以:
    CopyComponentIfExists(AllComponents{});
    等价
    CopyComponentIfExists(ComponentGroup<TransformComponent, SpriteRendererComponent, CircleRendererComponent>());
*/
}

请添加图片描述

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

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

相关文章

姿态估计端到端新方案 | DirectMHP:用于全范围角度2D多人头部姿势估计

前言 现有的头部姿势估计主要集中在具有预先检测到的正面头部的单个人&#xff0c;这依赖于单独训练的面部检测器&#xff0c;不能很好地泛化到完整的视点。在本文中&#xff0c;作者关注全范围 MPHPE 问题&#xff0c;并提出了一个名为 DirectMHP 的直接端到端简单基线&#x…

怎么给笔记本电脑外接两台显示器?

我们在办公室会看见不少同事的电脑不止一台显示器&#xff0c;多屏确实可以提高工作效率。有的游戏党也会选择给电脑外接显示器&#xff0c;带来绝佳的体验。 不过要怎么把将外部显示器连接到笔记本电脑上&#xff1f;驱动人生在这里教给大家给笔记本外接显示器的做法。 一、…

TensorFlow CNN 卷积神经网络实现人脸性别检测 完整教程 附完整代码

本文主要是实现了根据人脸识别性别的卷积神经网络,并对卷积过程中的提取特征进行了可视化.

HydroD 实用教程(二)有限元模型

目 录一、前言二、模型种类三、单元类型四、FEM文件五、参考文献一、前言 SESAM &#xff08;Super Element Structure Analysis Module&#xff09;是由挪威船级社&#xff08;DNV-GL&#xff09;开发的一款有限元分析&#xff08;FEA&#xff09;系统&#xff0c;它以 GeniE、…

【Linux command 09】tcpdump 命令

tcp一款sniffer工具&#xff0c;是Linux上的抓包工具&#xff0c;嗅探器语法tcpdump (选项)选项-c&#xff1a; 指定要抓取的包数量。注意&#xff0c;是最终要获取这么多个包。例如&#xff0c;指定"-c 10"将获取10个包&#xff0c;但可能已经处理了100个包&#xf…

激光slam学习笔记2--激光点云数据结构特点可视化查看

背景&#xff1a;不同厂商的激光点云结果存在一定差异&#xff0c;比如有些只有xyz&#xff0c;有些包含其他&#xff0c;如反光率、时间戳、ring等。如何快速判断是个值得学习的点 概要&#xff1a;对于rosbag类型的激光点云&#xff0c;介绍使用rviz快速查看点云结构特点 如…

JavaWeb--MySQL高级

MySQL高级1 约束1.1 概念1.2 分类1.3 非空约束1.4 唯一约束1.5 主键约束1.6 默认约束1.7 检查约束1.8 外键约束1.8.1 概述1.8.2 语法2 数据库设计2.1 数据库设计简介2.2 表关系(一对多)2.3 表关系(多对多)2.4 表关系(一对一)3 多表查询3.1 内连接查询3.2 外连接查询3.3 子查询3…

技能树基础——17四平方和(拉格朗日定理,嵌套循环)

题目&#xff1a;四平方和定理&#xff0c;又称为拉格朗日定理&#xff1a;每个正整数都可以表示为至多4个正整数的平方和。如果把0包括进去&#xff0c;就正好可以表示为4个数的平方和。比如&#xff1a;5 0^ 2 0^ 2 1^ 2 2^27 1^ 2 1^ 2 1^ 2 2^2 &#xff08;^符号表…

微服务项目【服务调用分布式session共享】

nginx动静分离 第1步&#xff1a;通过SwitchHosts新增二级域名&#xff1a;images.zmall.com 第2步&#xff1a;将本次项目的所有静态资源js/css/images复制到nginx中的html目录下 第3步&#xff1a;在nginx的核心配置文件nginx.conf中新增二级域名images.zmall.com访问映射…

h2database源码解析-表和索引

目录表索引MVPrimaryIndexMVDelegateIndexMVSecondaryIndex索引更新表 h2使用类MVTable表示数据库表&#xff0c;h2的表数据是基于主键排列的&#xff0c;这种表也叫做主键索引表。这也就意味着表必须有主键&#xff0c;如果没有主键&#xff0c;h2会自动生成一个主键_ROWID_&…

在Linux和Windows上编译datax-web-ui源码

记录&#xff1a;375场景&#xff1a;在CentOS 7.9操作系统上&#xff0c;使用apache-maven-3.8.7安装编译datax-web-ui源码。在Windows上操作系统上&#xff0c;使用apache-maven-3.8.7编译datax-web-ui源码。版本&#xff1a;JDK 1.8 node-v14.17.3 npm-6.14.13datax-web-ui开…

江苏五年制专转本应该复习几轮?

五年制专转本应该复习几轮&#xff1f; 据调查统计&#xff1a;2022年专转本17%的考生复习三轮及以上&#xff0c;23%的考生复习了两轮。这两类的考生录取率高至85%。可见复习轮数多&#xff0c;专转本上岸的概率也大。综合多方因素&#xff0c;建议同学们专转本复习四轮&#…

pointpillars Paper学习总结

Pointpillar Paper PointPillars提出了一种新的点云编码方式和3D转2D的方法&#xff0c;用2D卷积的方式实现目标检测而没有采用耗时的3D卷积&#xff0c;在速度和精度上达到了很好的平衡&#xff0c;其速度快、精度高、易于部署的特点使得其在工业界得到了广泛的应用。 处理思…

JavaWeb--JDBC

JDBC1 JDBC概述1.1 JDBC概念1.2 JDBC本质1.3 JDBC好处2 JDBC快速入门2.1 编写代码步骤2.2 具体操作3 JDBC API详解3.1 DriverManager3.2 Connection3.2.1 获取执行对象3.2.2 事务管理3.3 Statement3.3.1 概述3.3.2 代码实现3.4 ResultSet3.4.1 概述3.4.2 代码实现3.5 案例3.6 P…

2.Java基础【Java面试第三季】

2.Java基础【Java面试第三季】前言推荐2.Java基础01_字符串常量Java内部加载-上58同城的java字符串常量池面试code讲解intern()方法---源码解释02_字符串常量Java内部加载-下whyOpenJDK8底层源码说明递推步骤总结考查点03_闲聊力扣算法第一题字节跳动两数求和题目说明面试题解法…

rt-thread 移植调试记录

rt-thread 移植调试记录 记录rt-thread移植的过程。这里移植仅仅是利用rt-thread源码目录已经移植好的文件&#xff0c;组建自己的工程&#xff0c;不需要自己编写汇编完成底层移植。 1. 搭建基础工程 这里使用的是正点原子的潘多拉开发板&#xff0c;MCU为stm32l475。需要先…

【c++】类和对象:让你明白“面向一个对象有多重要”:构造函数,析构函数,拷贝构造函数的深入学习

文章目录 什么是面向对象&#xff1f;一&#xff1a;类是什么&#xff1f; 1.类的访问限定符 2.封装 3.类的实例化 4.this指针二&#xff1a;类的6个默认成员函数 1.构造函数 2.析构函数 3.拷贝构造函数什么是面向对象&#xff1f; c语言是面向…

window系统中安装Jupyter Notebook方法记录

1.初步感受Jupyter Notebook Jupyter Notebook 官网地址&#xff1a;Jupyter Notebook Jupyter Notebook&#xff08;此前被称为 IPython notebook&#xff09;是一个交互式笔记本&#xff0c;支持运行 40 多种编程语言。 Jupyter Notebook 的本质是一个 Web 应用程序&#xf…

Java体系最强干货分享—挑战40天准备Java面试,最快拿到offer!

如何准备java面试&#xff0c;顺利上岸大厂java岗位&#xff1f; 主攻Java的人越来越多&#xff0c;导致行业越来越卷&#xff0c;最开始敲个“hello world”都能进大厂&#xff0c;现在&#xff0c;八股、全家桶、算法等等面试题横行&#xff0c;卷到极致&#xff01;就拿今年…

聊聊什么是架构,你理解对了吗?

什么是架构?软件有架构?建筑也有架构?它们有什么相同点和不同点? 下面咱们就介绍一下,容易混淆的几个概念 一、系统与子系统 系统 泛指由一群有关联的个体组成,根据某种规则运作,能完成个别元件不能单独完成的工作的群体。它的意思是 “总体”、“整体”或“联盟” 子系…