模板
D通过模板实现泛型编程。模板声明 定义为:模板声明: template 模板标志符 ( 模板参数列表 ) { 声明定义 } 模板标志符: 标志符 模板参数列表 模板参数 模板参数 , 模板参数列表 模板参数: 类型参数 值参数 别名参数 模板类型参数: 标志符 标志符 模板类型参数特化 标志符 模板类型参数默认值 标志符 模板类型参数特化 模板类型参数默认值 模板类型参数特化: : 类型 模板类型参数默认值: = 类型 模板值参数: 声明 声明 模板值参数特化 声明 模板值参数默认值 声明 模板值参数特化 模板值参数默认值 模板值参数特化: : 条件表达式 模板值参数默认值: = 条件表达式 模板别名参数: alias 标志符 alias 标志符 模板别名参数默认值 模板别名参数默认值: = 类型
TemplateDeclaration: template TemplateIdentifier ( TemplateParameterList ) { DeclDefs } TemplateIdentifier: Identifier TemplateParameterList TemplateParameter TemplateParameter , TemplateParameterList TemplateParameter: TypeParameter ValueParameter AliasParameter TemplateTypeParameter: Identifier Identifier TemplateTypeParameterSpecialization Identifier TemplateTypeParameterDefault Identifier TemplateTypeParameterSpecialization TemplateTypeParameterDefault TemplateTypeParameterSpecialization: : Type TemplateTypeParameterDefault: = Type TemplateValueParameter: Declaration Declaration TemplateValueParameterSpecialization Declaration TemplateValueParameterDefault Declaration TemplateValueParameterSpecialization TemplateValueParameterDefault TemplateValueParameterSpecialization: : ConditionalExpression TemplateValueParameterDefault: = ConditionalExpression TemplateAliasParameter: alias Identifier alias Identifier TemplateAliasParameterDefault TemplateAliasParameterDefault: = Type无论模板是否被最终实例化,模板声明 的过程体在语法上必须是正确的。语义分析延迟到模板实例化时进行。模板有自己的作用域,模板过程体中可以包括类、结构、类型、枚举、变量、函数或者其它模板。
模板参数可以是类型、值或者符号。可以使用任何类型。值参数必须是整数型,在特化时它们必须被解析为整数常量。符号可以是任何非局部符号。
模板参数特化将值或类型约束为 模板参数 可以接受的值或类型。
模板参数默认值用在不提供 模板参数 时。
模板实例化
模板的实例体定义为:模板实例: 模板标志符 !( 模板参数列表 ) 模板参数列表: 模板参数 模板参数 , 模板参数列表 模板参数: 类型 赋值表达式 符号
TemplateInstance: TemplateIdentifer !( TemplateArgumentList ) TemplateArgumentList: TemplateArgument TemplateArgument , TemplateArgumentList TemplateArgument: Type AssignExpression Symbol一旦被实例化,位于模板内的声明,称做模板成员,就位于 TemplateInstance 作用域内:
template TFoo(T) { alias T* t; } ... TFoo!(int).t x; // 声明 x 为 int* 类型模板实例化可以有别名:
template TFoo(T) { alias T* t; } alias TFoo!(int) abc; abc.t x; // 声明 x 为 int* 类型一个 模板声明 拥有相同 模板参数列表 的多个实例将引用相同的实例。例如:
template TFoo(T) { T f; } alias TFoo(int) a; alias TFoo(int) b; ... a.f = 3; assert(b.f == 3); // a 和 b 引用相同的 TFoo 实例即使 模板实例 位于不同的模块中,这条规则也成立。
如果声明了拥有多个相同 模板标志符 的模板,并且它们参数个数不同或者采用不同的特化,那么它们不同。
例如,一个简单的泛型复制函数可以是这个样子:
template TCopy(T) { void copy(out T to, T from) { to = from; } }在使用模板之前,必须先使用具体的类型将其实例化:
int i; TCopy!(int).copy(i, 3);
实例化作用域
模板实例 总是在声明 模板声明 的作用域内执行,另外声明的模板参数被看作它们所推出的类型的别名。例如:
-------- module a --------- template TFoo(T) { void bar() { func(); } } -------- module b --------- import a; void func() { } alias TFoo!(int) f; // 错误:func 没有在模块 a 内定义以及:
-------- module a --------- template TFoo(T) { void bar() { func(1); } } void func(double d) { } -------- module b --------- import a; void func(int i) { } alias TFoo!(int) f; ... f.bar(); // 将调用 a.func(double)模板参数 的特化和默认值将在 模板声明 的作用域内估值。
参数推导
采用比较对应的模板参数(template parameter)和模板实参(template argument)的方法,模板实例中的模板参数的类型被推倒出来。对于每个模板参数,按照下面的顺序逐条应用规则直到每个参数的类型都被推倒出来:
- 如果参数没有指定一个特化,参数的类型被设为指定的模板实参。
- 如果类型特化依赖于一个类型参数,这个参数的类型就被设为与那个类型实参对应的类型。
- 如果在检查了所有类型实参之后还有类型参数没有被分配类型,它们就会被分配给在 模板参数列表 中位于相同位置的模板实参。
- 如果应用上述规则之后,还不能做到每个模板参数都精确的对应唯一一个类型,那么就被视为错误。
template TFoo(T) { } alias TFoo!(int) Foo1; // (1) T 被推导为 int alias TFoo!(char*) Foo2; // (1) T 被推导为 char* template TFoo(T : T*) { } alias TFoo!(char*) Foo3; // (2) T 被推导为 char template TBar(D, U : D[]) { } alias TBar!(int, int[]) Bar1; // (2) D 被推导为 int, U 被推导为 int[] alias TBar!(char, int[]) Bar2; // (4) 错误,D 既是 char 又是 int template TBar(D : E*, E) { } alias TBar!(int*, int) Bar3; // (1) E 是 int // (3) D 是 int*当考虑匹配时,一个类被认为可以匹配任何父类或接口:
class A { } class B : A { } template TFoo(T : A) { } alias TFoo!(B) Foo4; // (3) T 是 B template TBar(T : U*, U : A) { } alias TBar!(B*, B) Foo5; // (2) T 是 B* // (3) U 是 B
值参数
这个模板例子中,指定了一个为 10 的值参数:template foo(U : int, int T : 10) { U x = T; } void main() { assert(foo!(int, 10).x == 10); }
特化
模板可以通过在模板参数之后指定一个“:”和一个特化类型来将模板特化为使用某些指定的实参类型。例如:template TFoo(T) { ... } // #1 template TFoo(T : T[]) { ... } // #2 template TFoo(T : char) { ... } // #3 template TFoo(T,U,V) { ... } // #4 alias TFoo!(int) foo1; // 实例化 #1 alias TFoo!(double[]) foo2; // 实例化 #2 ,其中 T 为 double alias TFoo!(char) foo3; // 实例化 #3 alias TFoo!(char, int) fooe; // 错误,实参个数不匹配 alias TFoo!(char, int, int) foo4; // 实例化 #4当进行模板实例化时,会挑选匹配 模板参数列表 的特化度最高的模板。决定那个模板更为特化的方式同 C++ 处理偏序规则的方式相同。如果结果是模棱两可的,就是错误。
别名参数
别名参数使模板能够使用任何 D 符号参数化,包括全局名称、类型名称、模板名称以及模板实例名称。局部名字可能不能作为别名参数。这是 C++ 中将模板作为模板参数的做法的超集。- 全局名
int x; template Foo(alias X) { static int* p = &X; } void test() { alias Foo!(x) bar; *bar.p = 3; // 设置 x 为 3 int y; alias Foo!(y) abc; // 错误,y 是一个局部名称 }
- 类型名
class Foo { static int p; } template Bar(alias T) { alias T.p q; } void test() { alias Bar!(Foo) bar; bar.q = 3; // 将 Foo.p 设置为 3 }
- 模块名
import std.string; template Foo(alias X) { alias X.toString y; } void test() { alias Foo!(std.string) bar; bar.y(3); // 调用 std.string.toString(3) }
- 模板名
int x; template Foo(alias X) { static int* p = &X; } template Bar(alias T) { alias T!(x) abc; } void test() { alias Bar!(Foo) bar; *bar.abc.p = 3; // 设置 x 为 3 }
- 模板别名
int x; template Foo(alias X) { static int* p = &X; } template Bar(alias T) { alias T.p q; } void test() { alias Foo!(x) foo; alias Bar!(foo) bar; *bar.q = 3; // 设置 x 为 3 }
模板参数默认值
右端的模板参数(从最右端的那个开始向左连续的带有默认值的那些参数)可以有默认值:template Foo(T, U = int) { ... } Foo!(uint,long); // 实例化:T 为 uint ,U 为 long Foo!(uint); // 实例化:T 为 uint ,U 为 int template Foo(T, U = T*) { ... } Foo!(uint); // 实例化:T 为 uint ,U 为 uint*
隐式模板属性
如果模板有且只有一个成员,并且这个成员和模板同名的话,这个成员就被认为引用的是一个模板实例:template Foo(T) { T Foo; // 声明变量 Foo 为类型 T } void test() { Foo!(int) = 6; // 代替 Foo!(int).Foo }
类模板
类模板声明: class 标志符( 模板参数列表) [父类{, 接口类}] 类过程体
ClassTemplateDeclaration: class Identifier ( TemplateParameterList ) [SuperClass {, InterfaceClass }] ClassBody如果一个模板声明且仅声明了一个成员,并且那个成员是一个同模板同名的类:
template Bar(T) { class Bar { T member; } }则同下面的声明语义等价,称作 类模板声明 :
class Bar(T) { T member; }
递归模板
可以组合模板的各种特性来产生一些有趣的效果,例如在编译时对非平凡函数求值。例如,可以写一个计算阶乘的模板:template factorial(int n : 1) { enum { factorial = 1 } } template factorial(int n) { // 注意:‘.’用来在全局作用域而不是枚举作用域中查找模板 enum { factorial = n* .factorial!(n-1) } } void test() { printf("%d/n", factorial!(4)); // 打印 24 }
限制
模板不能用来给类添加非静态成员或函数。例如:class Foo { template TBar(T) { T xx; // 错误 int func(T) { ... } // 错误 static T yy; // Ok static int func(T t, int y) { ... } // Ok } }不能在函数内部声明模板。