C++98元编程技术解析_第1页
C++98元编程技术解析_第2页
C++98元编程技术解析_第3页
C++98元编程技术解析_第4页
C++98元编程技术解析_第5页
已阅读5页,还剩3页未读, 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

第第页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. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论