C++模板元编程实战:一个深度学习框架的初步实现.docx
《C++模板元编程实战:一个深度学习框架的初步实现.docx》由会员分享,可在线阅读,更多相关《C++模板元编程实战:一个深度学习框架的初步实现.docx(256页珍藏版)》请在课桌文档上搜索。
1、C+模板元编程实战:一个深度学习框架的初步实现1 .第一部分元编程基础技术2 .第1章基本技巧3 .第2章异类词典与PoliCy模板4 .第3章深度学习概述5 .第4章类型体系与基本数据类型6 .第5章运算与表达式模板7 .第6章基本层8 .第7章复合层与循环层9 .第8章求值与优化10 .后记方家休见笑,吾道本艰难第一部分元编程基础技术第1章基本技巧本章将讨论元编程与编译期计算所涉及的基本方法。我们首先介绍元函数,通过简单的示例介绍编译期与运行期所使用“函数”的异同。其次,在此基础上进一步讨论基本的顺序、分支、循环代码的书写方式。最后介绍一种经典的技巧奇特的递归模板式。上述内容可以视为基本的
2、元编程技术。而本书后续章节也可以视为这些技术的应用。掌握好本章所讨论的技术,是熟练使用C+模板元编程与编译期计算的前提。1.1 元函数与typjtraits1.1.1 元函数介绍C+元编程是一种典型的函数式编程,函数在整个编程体系中处于核心的地位。这里的函数与一般C+程序中定义与使用的函数有所区别,更接近数学意义上的函数是无副作用的映射或变换:在输入相同的前提下,多次调用同一个函数,得到的结果也是相同的。如果函数存在副作用,那么通常是由于存在某些维护了系统状态的变量而导致的。每次函数调用时,即使输入相同,但系统状态的差异会导致函数输出结果不同:这样的函数被称为具有副作用的函数。元函数会在编译期
3、被调用与执行。在编译阶段,编译器只能构造常量作为其中间结果,无法构造并维护可以记录系统状态并随之改变的量,因此编译期可以使用的函数(即元函数)只能是无副作用的函数。以下代码定义了一个函数,满足无副作用的限制,可以作为元函数使用。1 Constexprintfun(inta)returna+1;其中的ConSteXPr为C+11中的关键字,表明这个函数可以在编译期被调用,是一个元函数。如果去掉了这个关键字,那么函数fun将只能用于运行期,虽然它具有无副作用的性质,但也无法在编译期被调用。作为一个反例,考虑如下的程序:2 staticintcall_count=3;3 constexprintfu
4、n2(inta)4 5 returna+(call_count+);6 )这个程序片断无法通过编译它是错误的。原因是函数内部的逻辑丧失了“无副作用”的性质相同输入会产生不同的输出;而关键字ConSteXPr则试图保持函数的“无副作用”特性,这就导致了冲突。将其进行编译会产生相应的编译错误。如果将函数中声明的constexpr关键字去掉,那么程序是可以通过编译的,但fun2无法在编译期被调用,因为它不再是一个元函数了。希望上面的例子能让读者对元函数有一个基本的印象。在C+中,我们使用关键字ConSteXPr来表示数值元函数,这是C+中涉及的一种元函数,但远非全部。事实上,C+中用得更多的是类型元
5、函数即以类型作为输入和(或)输出的元函数。7 .1.2类型元函数从数学角度来看,函数通常可以被写为如下的形式:y=f()其中的3个符号分别表示了输入(X)、输出(y)与映射。通常来说,函数的输入与输出均是数值。但我们大可不必局限于此:比如在概率论中就存在从事件到概率值的函数映射,相应的输入是某个事件描述,并不一定要表示为数值。回到元编程的讨论中,元编程的核心是元函数,元函数输入、输出的形式也可以有很多种,数值是其中的一种,由此衍生出来的就是上一节所提到的数值元函数;也可以将C+中的数据类型作为函数的输入与输出。考虑如下情形:我们希望将某个整数类型映射为相应的无符号类型。比如,输入类型int时,
6、映射结果为UnSignedint;而输入为UnSignedlong时,我们希望映射的结果与输入相同。这种映射也可以被视作函数,只不过函数的输入是int、UnSignedIOng等类型,输出是另外的一些类型而已。可以使用如下代码来实现上述元函数:8 template9 structFun_usingtype=T;1011 template12 structFun-usingtype=unsignedint;1314 template15 structFun-usingtype=unsignedlong;16lFun-:typeh=3;读者可能会问:函数定义在哪儿?最初接触元函数的读者往往会有这样
7、的疑问。事实上,上述片断的18行已经定义了一个函数FlnL第10行则使用了这个Fun_:type函数返回unsignedint1所以第10行相当于定义了一个无符号整型的变量h并赋予值3。Fim_与C+一般意义上的函数看起来完全不同,但根据前文对函数的定义,不难发现,Fun_具备了一个元函数所需要的全部性质: 输入为某个类型信息T,以模板参数的形式传递到Fun_模板中; 输出为Fun_模板的内部类型type.gpFun_:type; 映射体现为模板通过特化实现的转换逻辑:若输入类型为im,则输出类型为UnSignedintl等等。在C+11发布之前,已经有一些讨论C+元函数的著作了。在C+模板元
8、编程一书中,将上述程序段中的16行所声明的FlnL视为元函数:认为函数输入是X时,输出为Fun_:typea同时,该书规定了所讨论的元函数的输入与输出均是类型。将一个包含了type声明的类模板称为元函数,这一点并无不妥之处:它完全满足元函数无副作用的要求。但作者认为,这种定义还是过于狭隘了。当然像这样引入限制,相当于在某种程度上统一了接口,这将带来一些程序设计上的便利性。但作者认为这种便利性是以牺牲代码编写的灵活性为代价的,成本过高。因此,本书对元函数的定义并不局限于上述形式。具体来说:并不限制映射的表示形式一像前文所定义的以COnSteXPr开头的函数,以及本节讨论的提供内嵌type类型的模
9、板,乃至后文中所讨论的其他形式的“函数”,只要其无副作用,同时可以在编译期被调用,都被本书视为元函数; 并不限制输入与输出的形式,输入与输出可以是类型,数值甚至是模板。在放松了对元函数定义的限制的前提下,我们可以在FUk的基础上再引入一个定义,从而构造出另一个元函数FUn:1 template2 usingFun=typenameFun_:type;34 Funh=3;FUn是一个元函数吗?如果按照C+模板元编程中的定义,它至少不是一个标准的元函数,因为它没有内嵌类型type。但根据本章开头的讨论,它是一个元函数,因为它具有输入(T),输出(FUn),同时明确定义了映射规则。那么在本书中,就将
10、它视为一个元函数。事实上,上文所展示的同时也是C+标准库中定义元函数的一种常用的方式。比如,C+11中定义了元函数std:enable_if,而在C+14中引入了定义std:enablejf_t,前者就像Fun_那样,是内嵌了type类型的元函数,后者则就像FUn那样,是基于前者给出的一个定义,用于简化使用。5 .1.3各式各样的元函数在前文中,我们展示了几种元函数的书写方法,与一般的函数不同,元函数本身并非是C+语言设计之初有意引入的,因此语言本身也没有对这种构造的具体形式给出相应的规定。总的来说,只要确保所构造出的映射是“无副作用”的,可以在编译期被调用,用于对编译期乃至运行期的程序行为产
11、生影响,那么相应的映射都可以被称为元函数,映射具体的表现形式则可以千变万化,并无一定之规。事实上,一个模板就是一个元函数。下面的代码片断定义了一个元函数,接收参数T作为输入,输出为Fun:6 template7 structFun;函数的输入可以为空,相应地,我们也可以建立无参元函数:12 3 4 5 6 7structFunusingtype=int;;constexprintfun()8 return1;9这里定义了两个无参元函数。前者返回类型im,后者返回数值10。基于C+14中对Constexpr的扩展,我们可以按照如下的形式来重新定义LLl节中引入的元函数:1 template2 c
12、onsteprintfun=a+1;这看上去越来越不像函数了,连函数应有的大括号都没有了。但这确实是一个元函数。唯一需要说明的是:现在调用该函数的方法与调用LLl节中元函数的方法不同了。对于LLl节的函数,我们的调用方法是fun(3),而对于这个函数,相应的调用方式则变成了funo除此之外,从编译期计算的角度来看,这两个函数并没有很大的差异。前文所讨论的元函数均只有一个返回值。元函数的一个好处是可以具有多个返回值。考虑下面的程序片断:3 template4 structFun-5 6 usingreference_type=int&;7 usingconst_reference_type=co
13、nstint&;8 usingvalue_type=int;9 );这是个元函数吗?希望你回答“是。从函数的角度上来看,它有输入(int),包含多个输出:Fun_:reference_typexFun_:const_reference_type-Fun_:value_type0一些学者反对上述形式的元函数,认为这种形式增加了逻辑间的耦合,从而会对程序设计产生不良的影响(见C+模板元编程)。从某种意义上来说,这种观点是正确的。但作者并不认为完全不能使用这种类型的函数,我们大可不必因噎废食,只需要在合适的地方选择合适的函数形式即可。10 1.4type_traits提到元函数,就不能不提及一个元函
14、数库:type_traitsotype_traits是由boost引入的,C+11将被纳入其中,通过头文件Iypejraits来引入相应的功能。这个库实现了类型变换、类型比较与判断等功能。考虑如下代码:1 std:remove_reference:typehl=3;2 std:remove_reference_th2=3;第1行调用Std:remove_reference这个元函数,将int&变换为im并以之声明了一个变量;第2行则使用std:remove_reference_t实现了相同的功能。std:remove_reference与std:remove_reference都是定义于typ
15、eraits中加元函数,其关家类似于1.1.2节中讨论的FUn_与FUnO通常来说,编写泛型代码往往需要使用这个库以进行类型变换。我们的深度学习框架也不例外:本书会使用其中的一些元函数,并在首次使用某个函数时说明其功能。读者可以参考C+标准模板库等书籍来系统性地了解该函数库。1.1.5 元函数与宏按前文对函数的定义,理论上宏也可以被视为一类元函数。但一般来说,人们在讨论C+元函数时,会把讨论范围的重点限制在ConSteXPr函数以及使用模板构造的函数上,并不包括宏。这是因为宏是由预处理器而非编译器所解析的,这就导致了很多编译期间可以利用到的特性,宏无法利用。典型事例是,我们可以使用名字空间将C
16、OnSteXPr函数与函数模板包裹起来,从而确保它们不会与其他代码产生名字冲突。但如果使用宏作为元函数的载体,那么我们将丧失这种优势。也正是这个原因,作者认为在代码中尽量避免使用宏。但在特定情况下,宏还是有其自身优势的。事实上,在构造深度学习框架时,本书就会使用宏作为模板元函数的一个补充。但使用宏还是要非常小心的。最基本的,作者认为应尽量避免让深度学习框架的最终用户接触到框架内部所定义的宏,同时确保在宏不再被使用时解除其定义。1.1.6 本书中元函数的命名方式元函数的形式多种多样,使用起来也非常灵活。在本书(以及所构造的深度学习框架)中,我们会用到各种类型的元函数。这里限定了函数的命名方式,以
17、使得程序的风格达到某种程度上的统一。在本书中,根据元函数返回值形式的不同,元函数的命名方式也会有所区别:如果元函数的返回值要用某种依赖型的名称表示,那么函数将被命名为xxx_的形式(以下划线为其后缀);反之,如果元函数的返回值可以直接用某种非依赖型的名称表示,那么元函数的名称中将不包含下划线形式的后缀。以下是一个典型的例子:1 template2 structAdd_3 consteprstaticintvalue=a+b;4 );56 template7 consteprintAdd=a+b;89 consteprint1=Add_:value;lconstexprintx2=Add;其中的
18、14行定义了元函数AeIeL;67行定义了元函数Add。它们具有相同的功能,只是调用方式不同:第9与10行分别调用了两个元函数,获取到返回结果后赋予Xl与x2。第9行所获取的是一个依赖型的结果(VahIe依赖于AdcL存在),相应地,被依赖的名称使用下划线作为后缀:Add_;而第10行在获取结果时没有采用依赖型的写法,因此函数名中没有下划线后缀。这种书写形式并非强制性的,本书选择这种形式,仅仅是为了风格上的统1.2模板型模板参数与容器模板相信在阅读了上节之后,读者已经建立起了以下的认识:元函数可以操作类型与数值;对于元函数来说,类型与数值并没有本质上的区别,它们都可视为一种“数据”,可以作为元
19、函数的输入与输出。事实上,C+元函数可以操作的数据包含3类:数值、类型与模板,它们统一被称为“元数据“,以示与运行期所操作的“数据”有所区别。在上一节中,我们看到了其中的前两类,本节首先简单讨论一下模板类型的元数据。1.2.1 模板作为元函数的输入模板可以作为元函数的输入参数,考虑下面的代码:1 templatetemplateclassTl,typenameT22 structFun_3 usingtype=typenameT1:type;4 ;56 templatetemplateclassTl,typenameT27 usingFun=typenameFUn_:type;89 Funh=
20、3;17行定义了元函数Fun,它接收两个输入参数:一个模板与一个类型。将类型应用于模板之上,获取到的结果类型作为返回值。在第9行,使用这个元函数并以std:remove_reference与int&作为参数传入。根据调用规则,这个函数将返回int,即我们在第9行声明了一个M类型的变量h并赋予值3。从函数式程序设计的角度上来说,上述代码所定义的FUn是一个典型的高阶函数,即以另一个函数为输入参数的函数。可以将其总结为如下的数学表达式(为了更明确地说明函数与数值的关系,下式中的函数以大写字母开头,而纯粹的数值则是以小写字母开头):Flm(T1,t2)=1(t2)10 2.2模板作为元函数的输出与数
21、值、类型相似,模板除了可以作为元函数的输入,还可以作为元函数的输出,但编写起来会相对复杂一些。考虑下面的代码:12345678templatestructFun_;templatestructFun-templateusingtype=std:add_lvalue_reference;;9 template10 structFun-11 template12 usingtype=std:remove-reference;13 );1415 template16 template17 usingFun=typenameFun_:!templatetype;1819 template20 usin
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- C+ 模板 编程 实战 一个 深度 学习 框架 初步 实现

链接地址:https://www.desk33.com/p-675841.html