模板
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
}
}
不能在函数内部声明模板。
本文围绕D语言的模板展开,介绍了模板参数可为类型、值或符号,阐述了模板参数特化、默认值等概念。还涉及模板实例化、实例化作用域、参数推导等内容,通过多个示例展示了别名参数在不同场景(全局名、类型名、模块名等)下的使用。
3871





