版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
第第页C++98元编程技术解析人们往往会将一个大问题(拆解)成许多小问题,通过解决一个个小问题,最终就能解决整个大问题。
若是拆解过后,这些小问题的处理逻辑不变,变化的只是输入状态,那么此时就是一种代码复用。对应编程世界,一种是自上而下的拆解组合方式,称为递归;一种是自下而上的拆解组合方式,称为迭代。
拆解后还能组合,拆解才有意义,递归和迭代本身就带有一种约束,必须具备起始状态和终止状态。若是没有起始状态,递归就没有起点,循环就没有开始;若是没有终止状态,递归就没有终点,循环就没有结束,
本文所说的元(编程)拆解技术,就是编译期的问题拆解与组合技术,编译期不像运行期那样能够动态地改变输入与输出状态,(C++)诞生了许多技术来解决这个问题,这便是后文要介绍的。
本文以一个需求为例,来讲解这些技术:
要求编写一个unroll(callb(ac)k)函数,该函数将调用N次传入的callback函数。
起始状态就是N,终止状态就是N为零,拆解后处理逻辑不变的小问题就是callback。
先不看文,你首先想到的是什么解法?文读毕,对比一下文中的各种拆解技术思路,获益更多。
原始递归法
模板元编程最开始就只支持递归这一种拆解方式,每次输入一个状态,依次产生下一个状态,若状态与递归终止状态相同,则结束。
采用这种方式,实现需求如下:
1namespace
cpp98
{23
//
declara(ti)on4
(te)mplate
void
unroll(F);56
template
7
struct
unroll_helper
{8
void
operator()(F
f)
{9
f();10
unroll(f);11
}12
};1314
//
terminated
state15
template
16
struct
unroll_helper
{17
void
operator()(F)
{}18
};1920
//
definition21
template
22
void
unroll(F
f)
{23
unroll_helper()(f);24
}2526
void
print_cpp98()
{27
std::puts("hello
cpp98");28
}29}3031int
main()
{32
cpp98::unroll(cpp98::print_cpp98);33
//
output:34
//
hello
cpp9835
//
hello
cpp9836}
由于函数模板不支持偏特化,于是需要借助一个unroll_helper类模板,来实现递归终止条件,再在unroll函数中调用该帮助类,不断拆解问题。
递归输入条件N为起始状态,每次拆解执行完成之后,通过N-1得到下一个状态,从而不断解决问题。
这个时期,C++的元编程拆解技术还很弱,一个简单的需求,实现起来也颇为繁琐。
可变参数模板
时间来到C++11,模板元编程迎来强大扩展,非常有用的一个新特性就是可变参数模板,它能够让我们摆脱递归这种原始拆解方式。
但是C++11还只是开始,基础组件不完善,所以并不能非常有效地实现目标。
什么意思呢?看如下这个不太完善的实现:
1namespace
cpp11
{23
template
4
class
index_sequence
{};56
template
7
void
unroll(F
f,
index_sequence)
{8
using
expand
=
std::size_t[];9
expand{
(f(),
Is)...
};10
}1112}131415int
main()
{16
cpp11::unroll([]
{
std::puts("hello
cpp11");
},17
cpp11::index_sequence());18}
原始递归法是采用不断递归来动态地产生状态,而有了可变参数模板,状态可以直接在编译期初期产生,从而直接拿来用就可以。
这里定义了一个index_sequence用来接收所有状态,然后借助一些逗号表达式技巧展开参数包,在参数包展开的过程当中,执行处理逻辑。
C++11起也支持Lambda,因此也不用再提供一个额外的调用函数。
这个实现的唯一缺点就是由于缺乏相应的组件,需要手动产生状态,导致使用起来较为麻烦。
完善版可变参数模板
C++14增加了std::index_sequence和std::make_index_sequence,于是就能将手动产生状态变成动态产生,完善实现。
代码如下:
1namespace
cpp14
{23
template
4
void
helper(F
f,
std::index_sequence)
{5
using
expand
=
std::size_t[];6
expand{
(f(),
Is)...
};7
}89
//
variable
template10
template
11
auto
unroll
=
[](auto
f)
{
//
generic
lambda12
helper(f,
std::make_index_sequence{});13
};14}1516int
main()
{17
cpp14::unroll([]
{
std::puts("hello
cpp14");
});18}
同时,C++14还支持variabletemplate和genericlambda,这进一步简化了实现。
FoldExpression
前面的方式是采用逗号表达式技巧来展开参数包,C++17支持Foldexpression,可以直接展开,因此代码得到进一步简化。
变成:
1namespace
cpp17
{23
template
4
void
helper(F
f,
std::index_sequence)
{5
((f(),
Is),
...);
//
fold
expression6
}78
template
9
auto
unroll
=
[](auto
f)
{
//
generic
lambda10
helper(f,
std::make_index_sequence{});11
};12}
constexprif
C++17的另一种拆解技术是借助constexprif,它的好处在于能够直接在本函数内判断终止状态,这样就不再需要去定义一个递归终止函数。
1namespace
cpp17
{2
//
variable
template
+
constexpr
if3
template
4
auto
unroll
=
[](auto
expr)
{5
if
constexpr
(N)
{6
expr();7
unroll(expr);8
}9
};10}1112int
main()
{13
cpp17::unroll([]
{
std::puts("hello
cpp17");
});14}
与原始递归法相比,这种方式除了消除递归终止函数,还免于编写一个额外的helper类,genericlambda更是减少了模板参数。
这是目前为止,最简洁的实现。
C++20双层Lambda法
有没有非递归的简洁拆解方式呢?当然也有。
看如下实现:
1namespace
cpp20
{23
template
constexpr
auto
unroll
=
[](auto
f)
{4
[f](std::index_sequence)
{5
((f(),
void(Is)),
...);6
}(std::make_index_sequence());7
};8}910int
main()
{11
cpp20::unroll([]
{
std::puts("hello
cpp20");
});12}
这里的关键是C++20的templatelambda,它支持为lambda编写模板参数,基于此才能够编写索引的模板参数。
Lambda函数里面再套一个Lambda函数,外层用于提供调用(接口),内层用于管理状态和处理调用。如果没有templatelambda,内层Lambda的std::index_sequence参数就无法写,也就接收不了状态。、
StructuredBindingPacks
原本有些新特性是应该在C++23就进入标准的,但由于种种原因,我们只有期望C++26能用上了。Structuredbindingpacks就是这么一个特性。
前面除了递归以外的所有拆解方法,都得借助std::index_sequence,这就是代码依旧复杂的原因所在。
有没有一种方式可以直接让我们访问参数包,而不必再定义一个参数为std::index_sequence的函数才能拿到那些参数包?Structuredbindingpacks就提供了这一能力。
这是P1061所提出的一种方式,让我们能够通过Structuredbindings直接得到参数包。
于是实现变为:
1namespace
p1061
{23
template
constexpr
auto
unroll
=
[](auto
f)
{4
auto
[...
Is]
=
std::make_index_sequence();5
((f(),
void(Is)),
...);6
};78}910int
main()
{11
p1061::unroll([]
{
std::puts("hello
p1061");
});12}
这种拆解技术才是最直观的方式,两行代码解决一切。
ExpansionStatements
另外一种方式就是我们在反射中经常使用到的一个特性:templatef(or)。
这种方式比StructuredBindingPacks更强大,是静态反射里面的一个扩展特性,能够支持编译期迭代。
对于本次需求的实现为:
1namespace
p1306
{2
template
constexpr
auto
unroll
=
[](auto
f)
{3
constexpr
std::array
dummy{};4
template
for
(auto6
};7}89int
m
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- T/GSET 001-2025不同品牌、不同型号电梯群控调度方法和要求
- T/CI 864-2024市政管道清淤施工及验收技术规范
- T/CAFFCI 70-2023食品添加剂 缬草油
- T/CSAE 350-2024轮胎静态刚度特性通用试验方法
- 2025~2026学年江苏省南通市通州区苏教版五年级上册期中数学试卷【附答案】
- 2026学年湖南省常宁市六年级数学期末点睛提升能力提升卷详细参考解析详细答案和解析
- Photoshop专业技能考试试题及详细答案
- 2026年国家公务员政治理论应知应会知识试题库与答案
- PES系统实操培训考核试题及详细答案
- 项目资料管理员操作规程
- 机器学习 课件全套 张旭东 第1-18讲 -概述、统计基础 -深度生成模型-3
- 2026住院医师规培-重庆-重庆住院医师规培(口腔全科)历年参考题库含答案详解
- 2026-2027学年五年级语文上册第三单元习作《故事新编》教学教学课件
- 2026建设银行校招笔试真题及答案
- GB/T 48023-2026数据中心冷板式液冷系统技术规范
- 2026年小学必读书目《安徒生童话》阅读测试试题及答案
- 食品加工厂厂长产品质量与生产效率绩效衡量表
- 旋挖成孔灌注桩施工监理实施细则
- DBJ33∕T 1020-2022 建设工程地质钻探安全技术规程 附条文说明
- 急救、生命支持类设备应急调配手册
- 孕产妇危重症紧急救治专家共识(2026年版)
评论
0/150
提交评论