开门见山
以下代码可展开模板参数包和展开函数参数包。
// 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函数内
-
ComponentGroup<>,是带有模板参数包的struct
-
ComponentGroup<Component…>,
Component…模板参数包由函数参数包AllComponents{}推断出来,像上面例子1的Func1(4);
由于AllComponents = ComponentGroup<TransformComponent,SpriteRendererComponent,CircleRendererComponent>
传入的函数参数包AllComponents{}
则能推断出模板参数包为
Component… = TransformComponent, SpriteRendererComponent,CircleRendererComponent -
得到了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 回归组件代码
由以上的步骤,不难理解原本的组件代码意思和流程了
- 传入AllComponents{}代表传入实参
- 第一个函数的模板参数包由函数参数包推断出来
- 第一个函数推断出来的模板参数包转发,传递给第二个函数
- 第二个函数接收到传过来的模板参数包
- 用折叠表达式解开模板参数包,并用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>());
*/
}