D语言中的模板

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

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

模板

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)的方法,模板实例中的模板参数的类型被推倒出来。

对于每个模板参数,按照下面的顺序逐条应用规则直到每个参数的类型都被推倒出来:

  1. 如果参数没有指定一个特化,参数的类型被设为指定的模板实参。
  2. 如果类型特化依赖于一个类型参数,这个参数的类型就被设为与那个类型实参对应的类型。
  3. 如果在检查了所有类型实参之后还有类型参数没有被分配类型,它们就会被分配给在 模板参数列表 中位于相同位置的模板实参。
  4. 如果应用上述规则之后,还不能做到每个模板参数都精确的对应唯一一个类型,那么就被视为错误。
例如:
	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
	    }
	}
	
不能在函数内部声明模板。
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值