1.7 概念
如果一个过程使用了一个类型,它就会依赖于该类型的语法、语义,还有其计算基的复杂性.在语法上,它依赖于一些确定的文字量和一些具有特定名字和签名(signature)的过程的存在;其语义依赖于基过程的语义;其复杂性依赖于基过程的时间和空间复杂性.如果用另一个具有同样性质的类型取代这个类型,程序将仍然是正确的.如果不是基于具体的类型,而是基于对类型的一些要求(通过语法和语义性质描述)来设计软件部件,例如设计库过程或数据结构,一定能提高它们的可用性.我们将这样的一组要求称为一个概念(concept).类型表示类别;而概念表示类属.
要描述概念,就需要有一些处理类型的机制,包括类型属性、类型函数和类型构造符.类型属性(typeattribute)是从一个类型到一个值的映射,它描述有关类型的某种特征.类型属性的例子如C++里提供的内部类型属性
sizeof(T),一个类型的对象的对齐方式,以及一个struct 的成员个数等.如果F是一个函数式过程类型,Arity(F)返回其输入的个数.
类型函数(typefunction)是从一个类型到一个从属类型的映射.类型函数的一个例子是:从给定的“到T的指针”得到类型T.在某些情况下,定义一个带有一个附加的整数参数的带索引(indexed)类型函数可能很有用.例如定义一个类型函数,它返回一个结构类型的第i个成员的类型(从0开始计).如果F是一个函数式过程类型,类型函数Codomain(F)返回其结果的类型.如果F是一个函数式过程且i类型构造符(typeconstructor)是从一些已有类型出发构造新类型的机制.举例说,pointer(T) 是一个内部提供的类型构造符,它从一个类型T 出发返回“指向T 的指针”类型;struct 是一种内部提供的n元类型构造符;一个结构模板是一个用户定义的n元类型构造符.
如果T是一个n元类型构造符,下面用TT0,...,Tn.1表示将它应用于类型T0,...,Tn.1.一个重要例子是pair(二元组),将其应用于规范类型T0和T1,就得到了一个struct 类型pairT0,T1 ,它有一个类型为T0的成员m0和一个类型为T1的成员m1.要保证类型pairT0,T1 本身也是规范的,就要求它的相等、赋值、析构和构造操作都是基于类型T0和T1,通过按两个成员分别做的方式定义.同样的技术可以用于任何其他元组类型,例如triple(三元组).第12章将说明如何实现pairT0,T1 , 并说明更复杂的类型构造符如何维持规范性.
采用更形式化一点的说法,一个概念(concept)是对一个或多个类型的需求的一个描述,这一描述以对类型上的过程、类型属性和类型函数的存在及其相关性质的方式给出.如果某个(某些)特定类型满足这些需求,就说这一概念被这个(或这些)类型建模(modeled),或者说它们是这一概念的模型(model).要断言概念C被类型T0,...,Tn.1建模,我们写C(T0,...,Tn.1).如果任意的满足概念C. 的类型也必定满足概念C,就说概念C. 精化(re.ne)概念C.如果C. 精化C,也说C弱于C..
类型概念(typeconcept)指定义在一个类型上的一个概念.举例说,C++定义了类型概念整数类型,它被无符号整数类型和有符号整数类型精化.而STL定义了类型概念序列(sequence).前面一直用基本类型概念Regular和
5.附录B说明了怎样在C++里定义类型属性和类型函数.
FunctionalProcedure,它们对应于前面给出的非形式化定义.我们可以用标准的数学记法来形式化地定义各种概念.要定义概念C,采用的写法是
C(T0,...,Tn.1).
E0∧E1∧...
∧Ek.1
其中的.读作“按定义相等”,Ti 是形式类型参数,Ej 是概念子句.概念子句可以有如下三种形式:
1.前面已定义的概念的应用,说明相应类型参数的一个子集建模此概念.
2.类型属性、类型函数或者过程签名,建模相应概念的类型里必须有它们.过程签名的形式是f:TT.,其中T是过程的定义域而T. 是其值域.类型函数签名的形式是F:C→ C.,其定义域和值域都是概念.
→
- 基于这些类型属性、类型函数和过程表述的公理.
我们有时也在第二类概念子句里的类型属性、类型函数或过程的签名之后包含它们的定义.这种定义的形式是x.→ F(x),其中的F是表达式.在特定模型里,这一定义可以被另一个不同的但与之协调的实现所覆盖.
举个例子,下面概念描述的是一元函数式过程:
UnaryFunction(F).
FunctionalProcedure(F)∧Arity(F)=1∧Domain:UnaryFunctionRegular
→
F .→ InputType(F,0)
下面概念描述的是同源的函数式过程:
HomogeneousFunction(F).FunctionalProcedure(F)
∧Arity(F)>0
∧ (.i, j ∈ N)(i,j.
∧Domain:HomogeneousFunctionRegular
→
F .→ InputType(F,0)
应该看到
(.F ∈ FunctionalProcedure)UnaryFunction(F)HomogeneousFunction(F)
.
抽象(abstract)过程指用类型和常量参数化的过程,带有对这些参数的要求.6 下面将为此使用函数模板和函数对象模板.参数放在template 关键字后面,类型参数用typename 引入,常量值用int 或其他整数类型名引入.对函数的需求通过requires 子句严格描述,该子句的内容是一个表达式,基于常量值、具体类型、形式参数、类型属性和类型参数的应用,值和类型的相等,相关概念,以及逻辑连接词等描述.7
这里是一个抽象过程的例子:
template<typename Op>
requires(BinaryOperation(Op))
Domain(Op) square(const Domain(Op)& x, Op op)
{
return op(x, x);
}
定义域里的值可能很大,所以这里通过常量引用的方式传递该参数.操作一般说比较小(例如是函数指针或很小的函数对象),因此用值方式传递.
概念描述的是一个类型的所有对象都满足的性质,其中的前条件(precon-dition)描述特定对象的性质.举例说,一个过程可能要求它的某个参数是素数,对整数类型的这一需求就需要用一个概念描述,其中的素数性质用一个前条件描述.函数指针的类型只描述了它的签名,未描述其语义性质.例如,一个过
6.本质上具有我们所采用的形式的抽象过程出现在1930vanderWaerden[1930],基于EmmyNoether和EmilArtin的演讲,GeorgeCollins和DavidMusser20世纪60年代后期和70年代前期的论文在计算机代数的研究中使用了它们.例如可以参考Musser[1975].
7.requires子句的完整语法见附录B.
程可能要求它的一个参数是指向函数的指针,并要求函数实现的是整数上的一个可结合的二元运算.有关整数上二元运算的要求可以用一个概念描述,而函数的结合性用一个前条件描述.
为一集类型定义一个前条件,需要使用一些数学记法,例如全称和存在量词,蕴涵等.举例说,要描述整数的素数性质,用下面定义
property(N:Integer)prime:N=1)∧(.u, v ∈ N)uv=n(|u|=1∨|v|=1)n .→ (|n|..
这里的第一行引入了一些形式类型参数和它们建模的概念,第二行说明一个性质并给出其签名,第三行是描述有关的参数应满足的特定性质的谓词.
一元函数式过程的规范性可以定义如下
property(F:UnaryFunction)regularunaryfunction:Ff.→ (.f. ∈ F)(.x, x. ∈ Domain(F))(f=f. ∧ x = x.)(f(x)=f.(x.))
.
这一定义很容易扩充到n元函数:将相等的函数作用于相等的参数得到相等的结果.通过扩充,我们要求规范的抽象函数的所有实例化也都是规范的.除非另有说明,本书后面说到过程时都是指规范过程,因此下面讨论中将不明确写出这一前条件.
项目
1.1请将相等、赋值、赋值构造操作扩充到不同类型的对象上.请考虑两个类型的解释和联系起跨类型的过程的公理.