模 板 2.014

2019-04-13 14:44发布

我想我可以担保地说没什么人理解了模板机制。 -- Richard Deyman

[color=red]模板(Templates)是 D 实现泛型编程(generic programming)的方法[/color]。模板通过 模板声明 进行定义:

模板声明:
template 模板标识符 ( 模板参数列表 )
{ 多个声明定义 }

模板标识符:
标识符
模板参数列表
模板参数
模板参数 , 模板参数列表

模板参数:
模板类型参数
模板值参数
模板别名参数
模板元组参数
[color=red]TemplateThisParameter (2.014)[/color]


无论模板是否被最终实例化,模板声明 的过程体 在语法上必须是正确的。语义分析延迟到
模板实例化 时进行。

模板有自己的作用域,模板过程体中可以 包括类、结构、类型、枚举、变量、函数或者其它模板。

模板参数可以是类型、具体值、符号或者元组(Tuple)。其类型可以使用任何类型。

值参数必须是整数型,在特化时它们必须被解析为整数常量。符号可以是任何 非局部符号。

元组(Tuple)是一种由 0 个或更多的 类型、值或 符号 组成的序列。

模板参数特化 将值或类型约束为 模板参数 可以接受的 值或类型。

模板参数默认值 用在不提供 模板参数 时。


17.1 显示模板 实例化(Template Instantiation)

模板显示实例化的方式:

模板实例:
模板标识符 !(模板参数列表 )

模板参数列表:
模板参数
模板参数 , 模板参数列表

模板参数:
类型
赋值表达式
符号

一旦被实例化,则位于该模板内的声明,也叫做模板成员(template members),就处于该 模板实例的 作用域内:

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* 类型

带有相同模板 参数列表 的 模板声明 的多重实例,在 隐示 转换之前,都会引用到[color=red]相同[/color]的实
例。

例如:
template TFoo(T) { T f; }
alias TFoo!(int) a; (!(int) ??)
alias TFoo!(int) b;
...
a.f = 3;
assert(b.f == 3); // a 和 b 引用相同的 TFoo 实例

即使 模板实例 完成于不同的模块中,这条规则也成立。

即使模板 实参 会被 隐式转换 成相同的模板 形参 类型,它们仍然会引用到[color=red]不同[/color]的实例:
struct TFoo(int x) { }
static assert(is(TFoo!(3) == TFoo!(2 + 1))); // 3 和 2+1 都是类型为 int 的 3
static assert(!is(TFoo!(3) == TFoo!(3u))); // 3u 和 3 是不同的类型

如果声明了拥有多个相同 模板标识符 的模板,并且它们参数个数不同或者采用不同的特
化,那么它们不同。

例如,一个简单的泛型复制函数可以是这个样子:
template TCopy(T)
{
void copy(out T to, T from)
{
to = from;
}
}

在使用模板之前,必须先使用具体的类型将其实例化:
int i;
TCopy!(int).copy(i, 3);


2 实例化作用域

模板实例 总是在声明 板声明 的作用域内执行,另外声明的模板参数被看作它们所推出的
类型的别名。

例如:
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)

模板参数 的特化和默认值将在 模板声明 的作用域内被求值。


3 参数推导

采用比较对应的模板形参(template parameter)和模板实参(template argument)的方法,模板实例中的模板参数的类型被推倒出来。

对于每个模板参数,按照下面的顺序 逐条应用规则 直到每个参数的类型都被 推倒 出来:
1. 如果参数没有指定一个 特化,参数的类型被设为指定的模板 实参。
2. 如果类型特化依赖于一个类型参数,这个参数的类型就被设为与那个类型实参对应的
类型。
3. 如果在检查了所有类型实参之后还有类型参数没有被分配类型,它们就会被分配给在
模板参数列表 中位于相同位置的模板实参。
4. 如果应用上述规则之后,还不能做到每个模板参数都精确的对应唯一一个类型,那么
就被视为错误。

例如:

template TFoo(T) { }
alias TFoo!(int) Foo1; // (1) T 被推导为 int
alias TFoo!(char*) Foo2; // (1) T 被推导为 char*
template TBar(T : T*) { }
alias TBar!(char*) Foo3; // (2) T 被推导为 char
template TAbc(D, U : D[]) { }
alias TAbc!(int, int[]) Bar1; // (2) D 被推导为 int, U 被推导为 int[]
alias TAbc!(char, int[]) Bar2; // (4) 错误,D 既是 char 又是 int
template TDef(D : E*, E) { }
alias TDef!(int*, int) Bar3; // (1) E 是 int
// (3) D 是 int*

从一个特例化进行的推演可以为多个参数提供值:
template Foo(T: T[U], U)
{
...
}
Foo!(int[long]) // 实例化 Foo:将 T 设置成 int,而 U 设置成 long

当考虑匹配时,一个类被认为可以匹配任何父类或接口:
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


4 模板类型参数(Template Type Parameters)

模板类型参数:
标识符
标识符 模板类型参数特化
标识符 模板类型参数默认值
标识符 模板类型参数特化 模板类型参数默认值

模板类型参数特化:
: 类型

模板类型参数默认值:
= 类型


4.1 特例化(Specialization)

模板可以通过在模板参数之后指定一个“:”和一个特化类型来将模板特化为使用某些指定的
实参类型。

例如:
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++ 处理偏序规则的方式相同。如果结果是模棱两可的,就是错误。


[color=red]2.014
Template This Parameters

TemplateThisParameter:
this TemplateTypeParameter
TemplateThisParameters are used in member function templates to pick up the type of the this reference.

import std.stdio;

struct S
{
const void foo(this T)(int i)
{
writeln(typeid(T));
}
}

void main()
{
const(S) s;
(&s).foo(1);
S s2;
s2.foo(2);
invariant(S) s3;
s3.foo(3);
}
Prints:

const(S)
S
invariant(S)[/color]


5 模板值参数(Template Value Parameters)

模板值参数:
单个声明
单个声明 模板值参数特化
单个声明 模板值参默认值
单个声明 模板值参数特化 模板值参默认值

模板值参数特化:
: 条件表达式

模板值参默认值:
[color=red]2.014
= __FILE__
= __LINE__[/color]
= 条件表达式

[color=red]2.014
The __FILE__ and __LINE__ expand to the source file name and line number at the point of instantiation.[/color]

这个模板例子中,指定了一个为 10 的值参数:
template foo(U : int, int T : 10)
{
U x = T;
}
void main()
{
assert(foo!(int, 10).x == 10);
}


17.6 模板别名参数(Template Alias Parameters)

模板别名参数:
alias 标识符
alias 标识符 模板别名参数特例化
alias 标识符 模板别名参数默认值
alias 标识符 模板别名参数特例化 模板别名参数默认值

模板别名参数特例化:
: 类型

模板别名参数默认值:
= 类型

别名参数使模板能够使用任何 D 符号参数化,包括全局名称、类型名称、模板名称以及模
板实例名称。

局部名字可能不能作为别名参数。这是 C++ 中将模板作为模板参数的做法的
超集。

• 全局名

int x;
template Foo(alias X)
{
static int* p = &X;
}
void test()
{
alias Foo!(x) bar;
*bar.p = 3; // 把 x 设置为 3
static int y;
alias Foo!(y) abc;
*abc.p = 3; // 把 y 设置为 3
}

• 类型名

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
}


7 模板元组参数(Template Tuple Parameters)

模板元组参数:
标识符 ...

如果在 模板参数列表 里的最后一个模板参数被声明为 模板元组参数,那么它会跟任意的
模板尾部参数进行匹配。参数的序列组成了一个 元组(Tuple)。一个 元组(Tuple) 不是一种类型、表达式或者符号。它是一种由类型、表达式或符号的任意混合的序列。

一个它的元素完全由类型组成的 元组(Tuple) 叫做 类型元组(TypeTuple)。一个它的元素完全由表达式组成的 元组(Tuple) 叫做 表达式元组(ExpressionTuple)。

一个 元组(Tuple) 可被用做参数列表并实例化别一个模板,或者被用作一个函数的参数列
表。

template Print(A ...)
{
void print()
{
writefln("args are ", A);
}
}
template Write(A ...)
{
void write(A a) // A 是一个 类型元组
// a 是一个 表达式元组
{
writefln("args are ", a);
}
}
void main()
{
Print!(1,'a',6.8).print(); // 输出结果:args 是 1a6.8
Write!(int, char, double).write(1, 'a', 6.8); // prints:args 是 1a6.8
}

模板元组(template tuples)可以由一个隐式实例化的函数模板的尾部参数的类型进行推导:

template Foo(T, R...)
{
void Foo(T t, R r)
{
writefln(t);
static if (r.length) // 如果有更多的实参
Foo(r); // 处理其余的实参
}
}
void main()
{
Foo(1, 'a', 6.8);
}

输出:
1a
6.8

Tuple 可以由一个委托或函数的传递为函数形参的参数列表的类型进行推导:

import std.stdio;
/* R 是 return 类型
* A 是第一个实参类型
* U 是剩余形参类型的 类型元组
*/
R delegate(U) Curry(R, A, U...)(R delegate(A, U) dg, A arg)
{
struct Foo
{
typeof(dg) dg_m;
typeof(arg) arg_m;
R bar(U u)
{
return dg_m(arg_m, u);
}
}
Foo* f = new Foo;
f.dg_m = dg;
f.arg_m = arg;
return &f.bar;
}
void main()
{
int plus(int x, int y, int z)
{
return x + y + z;
}
auto plus_two = Curry(&plus, 2);
writefln("%d", plus_two(6, 8)); // 输出 16
}

在一个 元组(Tuple) 里的元素数目可以使用 .length 特性获得。第 n 个元素可以通过使用 [n] 来索引该 元组(Tuple) 得到,而对于子元组(sub tuples)则可以通过分割语法(slicing syntax) 来创建。

元组(Tuple) 是静态的编译时实体,因此没有办法来动态更改、添加或删除元素。

如果一个带有元组(Tuple)参数的模板跟一个不带元组(Tuple)参数的模板都完全匹配于某一个模板实例,则不带有 模板元组参数 的模板会选择。


8 模板参数默认值

尾端(最右边)的模板参数可以被给定默认值:

template Foo(T, U = int) { ... }
Foo!(uint,long); // 实例化 Foo:T 替换为 uint ,U 替换为 long
Foo!(uint); // 实例化 Foo:T 替换为 uint ,U 替换为 int
template Foo(T, U = T*) { ... }
Foo!(uint); // 实例化 Foo:T 替换为 uint ,U 替换为 uint*


9 隐式模板特性

如果模板有且只有一个成员,并且这个成员和模板同名的话,这个成员就被认为引用的是一
个模板实例:

template Foo(T)
{
T Foo; // 声明变量 Foo 为类型 T
}
void test()
{
Foo!(int) = 6; // 代替 Foo!(int).Foo
}


10 类模板(Class Templates)

类模板声明:
class 标识符 ( 模板参数列表 ) [父类 {, 接口类 }] 类过程体如果一个模板声明且仅声明了一个成员,并且那个成员是一个同模板同名的类:

template Bar(T)
{
class Bar
{
T member;
}
}

则同下面的声明语义等价,称作 类模板声明:

class Bar(T)
{
T member;
}


11 结构、联合以及接口模板(Struct, Union, and Interface Templates)

跟类模板类似,结构(struct)、联合(union)以及接口(interface)都可以通过提供一个参数列表来传递进模板。


12 函数模板(Function Templates)

如果一个模板声明且仅声明了一个成员,并且那个成员是一个同模板同名的函数:

函数模板声明:
类型 标识符 ( 模板参数列表 ) ( 函数参数列表 ) 函数体
一个用于计算类型为 T 的平方的函数模板就是:

T Square(T)(T t)
{
return t * t;
}

函数模板可以通过使用 !(TemplateArgumentList) 进行显式的实例化:
writefln("The square of %s is %s", 3, Square!(int)(3));

或者进行隐式实例化,而此时的 模板参数列表 则由函数形参的类型进行 推导:

writefln("The square of %s is %s", 3, Square(3)); // T 被推导为 int

那些要被隐式推导的函数模板类型参数不可能进行特例化:

void Foo(T : T*)(T t) { ... }
int x,y;
Foo!(int*)(x); // 正确,T 不是推导自函数形参
Foo(&y); // 错误,T 进行了特例化

不能被隐式推导的模板形参可以有默认值:

void Foo(T, U=T*)(T t) { U p; ... }
int x;
Foo(&x); // T is int, U is int*


13 递归模板(Recursive Templates)

可以组合模板的各种特性来产生一些有趣的效果,例如在编译时对非平凡函数求值。例如,
可以写一个计算阶乘的模板:

template factorial(int n : 1)
{
enum { factorial = 1 }
}
template factorial(int n)
{
enum { factorial = n* factorial!(n-1) }
}
void test()
{
writefln("%s", factorial!(4)); // 输出 24
}


14 限制(Limitations)

模板不能用来给类添加非静态成员或函数。

例如:
class Foo
{
template TBar(T)
{
T xx; // 错误
int func(T) { ...} // 错误
static T yy; // 正确
static int func(T t, int y) { ...} // 正确
}
}


[color=red]不能在函数内部声明模板。[/color]


。。。。。。。。。。