C
语言的继任者?—— D
语言
现在看起来,大部分“新”的编程语言都跌入了两大“误区”:学术界关注的新语法变化和大公司关注的RAD(Rapid Application Development 快速应用程序开发)与Web开发,也许是时候产生一种实用型的新型语言了——正是在这种背景下,D语言诞生了。
在此,必须先提一下C语言。C语言是从BCPL语言和B语言演化而来的。1967年,Martin Richards开发出了BCPL(Basic Compound Programming Language)语言,其目的是为了编写操作系统和编译器。1970年,Ken Thompson在BCPL语言的基础上开发出了B语言,并在DEC PDP-7计算机上利用B语言实现了第一个Unix操作系统。
C语言是贝尔实验室的Dennis Ritchie在B语言的基础上开发出来的,并被逐渐用于Unix操作系统的系统软件和应用软件的开发上。这就是说,C语言的前身是B语言,那么现在出现的D语言呢?它是什么一种类型的语言呢?那C语言会不会像B语言一样会被取代呢?接着往下看,就会明白了。
最初,D语言由Walter Bright作为对C和C++语言的再设计而孕育于1999年12月,并在Walter Bright朋友与同事的建议下不断成长与发展。
可在
http://www.digitalmars.com/d/dcompiler.html 处下载最新版本的Win32及x86 Linux编译器试用;并可在
http://home.earthlink.net/~dvdfrdmn/d处下载GCC的D语言前端编译器及
http://gdcmac.sourceforge.net/处下载GNU for Mac OS X。
下面以Win32版本的D语言编译器来做简单说明:
相关文件
/dmd/bin/dmd.exe
D语言编译器
/dmd/bin/shell.exe
一个简单的命令行环境
/dmd/bin/sc.ini
编译器全局设置
/dmd/lib/phobos.lib
D语言运行时库
系统需求
32位Windows操作系统,如Windows XP
Dmd.zip for Win32(编译器)
Dmc.zip for Win32(链接器和工具)
安装
D语言中所有的工具都为命令行工具,所以必须在“命令提示符”窗口中运行。解压文件到一个特定的目录,dmd.zip将会创建名为/dmd的目录;dmc.zip将会创建名为/dm的目录。键入/dmd/bin/shell all.sh来运行。在目录/dmd/samples/d/下面,有一些简单的示例文件。
编译器参数与选项
dmd 文件名
-选项
文件名
扩展名
|
文件类型
|
无
|
D源代码
|
.d
|
D源代码
|
.di
|
D接口文件
|
.obj
|
目标文件
|
.lib
|
库文件
|
.exe
|
生成的可执行文件
|
.def
|
模块文件
|
.res
|
资源文件
|
-c
只编译,不链接
-cov
代码覆盖分析
-D
生成文档
-Dfdir
把文档写到dir目录中
-Dfnama
文档文件名为name
-d
允许“反对”功能
-debug
以debug模式编译
-debug=level
以小于等于level级别的debug模式编译
-debug=ident
以ident为识别符编译为debug模式
-g
加入符号调试信息
-H
生成D接口文件
-Hddir
把D接口文件写到dir目录中
-Hfname
生成的D接口文件名为name
--help
列出帮助信息
-inline
把函数展开为内联函数
-Ipath
引入(输入)库路径;通过分号可分隔不同的路径名,当有多个路径时,会以同一顺序进行搜索
-Lflag
把flag传递给链接器,如:/ma/li
-O
优化
-o-
不生成目标文件
-oddir
把目标文件写入到dir目录中
-ofname
把输出目录中的输出文件名设为name
-op
通常生成目标文件名时,将会去除 .d源代码文件的路径,而此选项将保留它
-profile
对生成代码的运行时性能作简单评价
-quiet
只显示重要的编译器信息
-release
编译为release版本
-unittest
编译为unittest代码,但仍打开断言
-v
显示详细信息
-version=level
编译为大于等于level级别的版本代码
-version=ident
以ident为识别符编译为版本代码
-w
打开警告信息
链接器
通常来说,在使用dmd编译器成功编译之后,就会直接调用链接器,如果不想链接,需在编译器加上-c选项。
D语言程序必须链接到phobos.lib运行时库,在此库之前,还必须链接C语言运行时库snn.lib。只要库文件的目录在LIB环境变量路径名中,这会自动完成,通常会这样设置LIB:
Set LIB=/dmd/lib;/dm/lib
如果dmd.exe在当前目录找不到链接器,就会到PATH路径中查找,如果想要使用一个特定的链接器,请设置LINKCMD环境变量,如:
set LINKCMD=/dm/bin/link
另外,dmd会在同一目录中查找sc.ini初始化文件,如果此文件存在,那么文件中的环境变量设置将会取代现有的任何设置。环境变量跟在[Environment]之后,以name=value形式出现,注释是那些以分号打头的行,如:
; sc.ini file for dmd
; Names enclosed by %% are searched for in the existing environment
; and inserted. The special name %@P% is replaced with the path
; to this file.
[Environment]
LIB="%@P%/../lib";/dm/lib
DFLAGS="-I%@P%/../src/phobos"
LINKCMD="%@P%/../../dm/bin"
DDOCFILE=mysettings.ddoc
D
接口文件
当在D源代码文件中处理一个引入库的声明时,编译器会在D源代码文件对应的目录中查找此引入库,并从中提取源代码所需的信息。换句话说,编译器只会查找对应的D接口文件。接口文件中只含有引入一个模块所需的信息,而不是此模块的完整实现。
此处不使用D源代码文件,而使用D接口文件的好处是:
Ø D接口文件通常来说体积更小,相比D源代码代码,可以被更快速地处理。
Ø 可用于隐藏源代码,例如,接口文件可与目标文件一同发售,而不用附带完整的源代码文件。
Ø D接口文件可通过对D源代码文件加上编译器选项-H生成,文件名后缀为 .di。此时当编译器解析一个输入库的声明时,它首先查找相应的 .di D接口文件,再查找D源代码文件。
Ø D接口文件承担了一部分相当于C++头文件的任务,但不像C++头文件那样是必须的,同时也不是D语言的一部分,它只是编译器的一种特性,在构建过程中起到优化的作用。
Bugs
当前版本的D语言中还存在以下一些bug:
² 编译器有时会把行号弄错
² D运行时库phobos的功能还不是很足够
² 需要编写一个工具来把C语言头文件转换为D语言输入文件
² 数组赋值操作还未完成
² 成员函数的前置条件与后置条件都不能继承
² 不能在IDDE中运行
D
语言
PK
其他语言
下表是D语言与C、C++、C#、Java语言的大体功能对比。虽然很多语言功能都可以通过标准库得到加强,但下表只对比了语言本身的内置功能;此处只对比了官方标准化的功能,而没有对比诸如提议的功能、beta版本、或扩展功能。
功能
|
D
|
C
|
C++
|
C#
|
Java
|
垃圾回收
|
Yes
|
No
|
No
|
Yes
|
Yes
|
函数
|
|
|
|
|
|
函数代理
|
Yes
|
No
|
No
|
Yes
|
No
|
函数重载
|
Yes
|
No
|
Yes
|
Yes
|
Yes
|
函数返回参数
|
Yes
|
Yes
|
Yes
|
Yes
|
No
|
嵌套函数
|
Yes
|
No
|
No
|
No
|
No
|
数据函数
|
Yes
|
No
|
No
|
No
|
No
|
动态终止
|
Yes
|
No
|
No
|
No
|
No
|
安全类型参数
|
Yes
|
No
|
No
|
Yes
|
Yes
|
数组
|
|
|
|
|
|
轻量型数组
|
Yes
|
Yes
|
Yes
|
No
|
No
|
可变数组
|
Yes
|
No
|
No
|
No
|
No
|
位数据
|
Yes
|
No
|
No
|
No
|
No
|
内置字符串支持
|
Yes
|
No
|
No
|
Yes
|
Yes
|
数组限幅
|
Yes
|
No
|
No
|
No
|
No
|
数组边界检查
|
Yes
|
No
|
No
|
Yes
|
Yes
|
数组结合
|
Yes
|
No
|
No
|
No
|
No
|
强类型定义
|
Yes
|
No
|
No
|
No
|
No
|
字符串转换
|
Yes
|
No
|
No
|
Yes
|
No
|
别名
|
Yes
|
Yes
|
Yes
|
No
|
No
|
面向对象编程
|
|
|
|
|
|
面向对象
|
Yes
|
No
|
Yes
|
Yes
|
Yes
|
多重继承
|
No
|
No
|
Yes
|
No
|
No
|
接口
|
Yes
|
No
|
Yes
|
Yes
|
Yes
|
操作符重载
|
Yes
|
No
|
Yes
|
Yes
|
No
|
模块
|
Yes
|
No
|
Yes
|
Yes
|
Yes
|
动态类加载
|
No
|
No
|
No
|
Yes
|
Yes
|
嵌套类
|
Yes
|
Yes
|
Yes
|
Yes
|
Yes
|
内部适配器类
|
Yes
|
No
|
No
|
No
|
Yes
|
协变返回类型
|
Yes
|
No
|
Yes
|
No
|
Yes
|
属性
|
Yes
|
No
|
No
|
Yes
|
No
|
性能
|
|
|
|
|
|
内联汇编
|
Yes
|
Yes
|
Yes
|
No
|
No
|
对硬件的直接访问
|
Yes
|
Yes
|
Yes
|
No
|
No
|
轻量级对象
|
Yes
|
Yes
|
Yes
|
Yes
|
No
|
显式内存分配控制
|
Yes
|
Yes
|
Yes
|
No
|
No
|
不依赖于虚拟机
|
Yes
|
Yes
|
Yes
|
No
|
No
|
直接生成本地代码
|
Yes
|
Yes
|
Yes
|
No
|
No
|
泛型编程
|
|
|
|
|
|
模板类
|
Yes
|
No
|
Yes
|
Yes
|
Yes
|
模板函数
|
Yes
|
No
|
Yes
|
No
|
Yes
|
隐式模板函数实例化
|
No
|
No
|
Yes
|
No
|
No
|
部分及显式特化
|
Yes
|
No
|
Yes
|
No
|
No
|
模板值参数
|
Yes
|
No
|
Yes
|
No
|
No
|
模板之模板参数
|
Yes
|
No
|
Yes
|
No
|
No
|
Mixins(混合插入)
|
Yes
|
No
|
No
|
No
|
No
|
静态if
|
Yes
|
No
|
No
|
No
|
No
|
基于类型的表达式条件编译
|
Yes
|
No
|
No
|
No
|
No
|
typeof
|
Yes
|
No
|
No
|
Yes
|
No
|
for each
|
Yes
|
No
|
No
|
Yes
|
Yes
|
隐式类型推论
|
Yes
|
No
|
No
|
No
|
No
|
可靠性
|
|
|
|
|
|
略语编程
|
Yes
|
No
|
No
|
No
|
No
|
单元测试
|
Yes
|
No
|
No
|
No
|
No
|
静态构建指令
|
Yes
|
No
|
No
|
Yes
|
Yes
|
有保证的初始化
|
Yes
|
No
|
No
|
Yes
|
Yes
|
自动析构函数
|
Yes
|
No
|
Yes
|
Yes
|
No
|
异常处理
|
Yes
|
No
|
Yes
|
Yes
|
Yes
|
try-catch-finally
|
Yes
|
No
|
No
|
Yes
|
Yes
|
线程同步原语
|
Yes
|
No
|
No
|
Yes
|
Yes
|
兼容性
|
|
|
|
|
|
C风格语法
|
Yes
|
Yes
|
Yes
|
Yes
|
Yes
|
枚举类型
|
Yes
|
Yes
|
Yes
|
Yes
|
Yes
|
支持所有C语言类型
|
Yes
|
Yes
|
No
|
No
|
No
|
80位浮点数
|
Yes
|
Yes
|
Yes
|
No
|
No
|
复数与虚数
|
Yes
|
Yes
|
No
|
No
|
No
|
支持访问C语言
|
Yes
|
Yes
|
Yes
|
No
|
No
|
可用现有的调试器
|
Yes
|
Yes
|
Yes
|
No
|
No
|
结构成员数据排列控制
|
Yes
|
No
|
No
|
No
|
No
|
生成标准目标文件
|
Yes
|
Yes
|
Yes
|
No
|
No
|
预处理宏
|
No
|
Yes
|
Yes
|
No
|
No
|
其他
|
|
|
|
|
|
条件编译
|
Yes
|
Yes
|
Yes
|
Yes
|
No
|
源代码中支持Unicode
|
Yes
|
Yes
|
Yes
|
Yes
|
Yes
|
编制注释文档
|
Yes
|
No
|
No
|
Yes
|
Yes
|
C++
程序员怎样转换到
D
语言
每一个有经验的C++程序员都积累了自己的一套编程风格与技巧,而这也是C++程序员的第二特征。有时,当学习一门新语言时,会不由自主地带入自己习惯的风格,这样就很难完全以新语言的特色来完成同样的任务。以下是一些常用的C++编程技巧,并演示如何在D语言中完成。
定义构造函数
C++中的构造函数必须与类名相同
class Foo
{
Foo(int x);
};
在D语言中通过关键字this来定义
class Foo
{
this(int x) { }
}
基类初始化
在C++中,可通过基类初始化语法来调用基类构造函数
class A { A() {... } };
class B : A
{
B(int x)
: A() //调用基类构造函数
{ ...
}
};
在D语言中,基类构造函数通过super语法调用
class A { this() { ... } }
class B : A
this(int x)
{ ...
super(); //调用基类构造函数
...
}
此处C++的优越之处在于,基类的构造函数,可放在继承类构造函数的任何地方;但D语言也都通过一个构造函数调用另一个构造函数达到此目的。
class A
{
int a;
int b;
this() { a = 7; b = foo(); }
this(int x)
{
this();
a = x;
}
}
甚至在构造函数调用之前,成员也能被初始化为常量,故以上代码可改写为:
class A
{
int a = 7;
int b;
this() { b = foo(); }
this(int x)
{
this();
a = x;
}
}
结构比较
在C++中,可以用一种简单、方便的方式来定义一个结构
struct A x, y;
...
x = y;
以上不是对结构进行比较,如果要比较结构,请看以下代码:
#include <string.h>
struct A x, y;
inline bool operator==(const A& x, const A& y)
{
return (memcmp(&x, &y, sizeof(struct A)) == 0);
}
...
if (x == y)
...
注意,在此处必须对每一个需要比较的结构完成操作符重载,而在实现中不会有任何形式的类型检查,所以,C++不会去理会是否(x == y)此时发生了什么,而必须手工去实现特定的操作符重载,以验证此时到底发生了什么。
在用memcmp()实现的 == 操作符重载中,潜伏了一个严重的bug,因为结构中不同数据的排列方式,会有一些“填充数据”存在,而C++是不允许这些“填充数据”被赋值;这样,即使两个结构中每个成员都有相同的值,也会因为这些“填充数据”的不同而导致最终比较的结果不一致。
为了解决这个问题,可以进行成员比较,但不幸的是,这种方法也不太可靠,因为:1、如果结构中新添了一个成员,而在 == 操作符比较中忘了加入对这种成员的支持;2、浮点数的比较会存在差异,即使它们的位模式匹配。因此,在C++中,对此没有一个稳健的解决方案。
在D语言中,以一种非常明白、直截了当的方式进行。
A x, y;
...
if (x == y)
...
创建一个新的typedef
类型
在C++中,typedef的功能非常弱,它不能真正引入一种新的类型,而在底层,编译器也不会把一个typedef类型与它所代表的类型区分开。
#define HANDLE_INIT
((Handle)(-1))
typedef void *Handle;
void foo(void *);
void bar(Handle);
Handle h = HANDLE_INIT;
foo(h);
//没有捕捉到的bug
bar(h);
//OK
C++的方法是创建一个哑元结构,它的唯一目的就是进行类型检查,并对新类型时行重载。
#define HANDLE_INIT
((void *)(-1))
struct Handle
{
void *ptr;
//默认构造函数
Handle() { ptr = HANDLE_INIT; }
Handle(int i) { ptr = (void *)i; }
//转换为其所代表的类型
operator void*() { return ptr; }
};
void bar(Handle);
Handle h;
bar(h);
h = func();
if (h != HANDLE_INIT)
...
而在D语言中,没有必要为了合乎语言习惯而使用上述的写法,只需要编写:
typedef void *Handle = cast(void *)-1;
void bar(Handle);
Handle h;
bar(h);
h = func();
if (h != Handle.init)
...
此处要注意,对typedef而言,是怎样提供一个默认构造函数的。
友元
在C++中,有时,两个有紧密关系但却不是继承来的类,需要访问各自的私有成员,此时就必须声明为友元:
class A
{
private:
int a;
public:
int foo(B *j);
friend class B;
friend int abc(A *);
};
class B
{
private:
int b;
public:
int bar(A *j);
friend class A;
};
int A::foo(B *j) { return j->b; }
int B::bar(A *j) { return j->a; }
int abc(A *p) { return p->a; }
在D语言中,友元的访问是通过同一模块中的某个成员来隐式实现的。D语言的做法是,有紧密关系的类,应该在同一module(模块)中。这是有道理的,通过隐式地允许对其他模块成员的友元访问,巧妙地解决了这个问题:
module X;
class A
{
private:
static int a;
public:
int foo(B j) { return j.b; }
}
class B
{
private:
static int b;
public:
int bar(A j) { return j.a; }
}
int abc(A p) { return p.a; }
此时的私有属性可防止来自其他模块的、对成员的访问。
操作符重载
在C++中,通过一个结构来创建一个新的支持算术运算的数据类型,可非常方便地进行操作符重载,并把它与整数进行比较:
struct A
{
int operator < (int i);
int operator <= (int i);
int operator > (int i);
int operator >= (int i);
};
int operator < (int i, A &a) { return a > i; }
int operator <= (int i, A &a) { return a >= i; }
int operator > (int i, A &a) { return a < i; }
int operator >= (int i, A &a) { return a <= i; }
算上加、减、乘、除,共需要8个函数。
D语言意识到比较操作符是一种基本的必须类型,所以只需要一个函数就行了:
struct A
{
int opCmp(int i);
}
编译器根据cmp函数,自动解析所有的<、<=、>、>=操作符,同样,也会处理好那些左操作数不是一个对象引用的情况。
对其他类型的操作符重载,也是适用的,在D语言中使用操作符重载可不那么乏味,且利于减少错误,因为达到同样效果,D语言的代码量更少。
命名空间
在C++中,通常是在一个命名空间名称前加上using声明,以便在当前作用域中使用:
namespace Foo
{
int x;
}
using Foo::x;
而在D语言中,使用module取代了命名空间和#include,而且alias声明取代了using声明:
/** Module Foo.d **/
module Foo;
int x;
/**另一个module **/
import Foo;
alias Foo.x x;
alias比单一目的的using声明更加灵活,alias也能被用于重命名符号(symbol)、引用模板成员、引用嵌套类类型,等等。
RAII (Resource Acquisition Is Initialization)
资源的获取是通过初始化
在C++中,类似内存之类的资源,全部需要显式地处理。因为在离开作用域时,析构函数会被自动调用,所以经常把释放资源的代码放在析构函数中:
class File
{
Handle *h;
~File()
{
h->release();
}
};
在D语言中,所有的资源释放问题都只不过是跟踪与释放内存,这在D语言中,是通过垃圾回收机制自动完成的。在资源中,使用率排在第二的是信号量与互斥锁,也是由D语言中的同步声明语句自动完成的。与C++相比,涉及到RAII的问题只有auto类,auto类在离开它们的作用域时,会自动运行析构函数。
auto class File
{
Handle h;
~this()
{
h.release();
}
}
void test()
{
if (...)
{ auto File f = new File();
...
} // 即使是通过异常退出作用域,也会调用f.~this()
}
属性
在C++中,一般惯例是定义一个代码域,其中带有面向对象编程的get与set函数:
class Abc
{
public:
void setProperty(int newproperty) { property = newproperty; }
int getProperty() { return property; }
private:
int property;
};
Abc a;
a.setProperty(3);
int x = a.getProperty();
所有的这些都非常简单,只是需要多打一点字而已,但在一个大型程序中,对getProperty()与setProperty()过多调用,会造成代码更加难以阅读。
在D语言中,可使用普通的域语法来完成get与set属性,并且get和set会调用相应的方法。
class Abc
{
// set
void property(int newproperty) { myprop = newproperty; }
// get
int property() { return myprop; }
private:
int myprop;
}
Abc a;
a.property = 3;
//等于a.property(3)
int x = a.property; //等于int x = a.property()
这样,D语言中的属性就能被当作一个简单的域名,另外,在一个继承类需要重载它们时,也省去了冗长的get与set属性定义,并且也容易实现接口类。
递归模板
对模板的一种高级应用是递归展开它们,并以特定的实现结束。一个计算价乘的模板如下:
template<int n> class factorial
{
public:
enum { result = n * factorial<n - 1>::result };
};
template<>class factorial<1>
{
public:
enum { result = 1 };
};
void test()
{
printf("%d/n", factorial<4>::result); //输出24
}
关于此示例的D语言版本是大致相同的,尽管有一点简单,但却是把单一模板成员提升到了括号中的命名空间:
template factorial(int n)
{
enum { factorial = n * .factorial!(n-1) }
}
template factorial(int n : 1)
{
enum { factorial = 1 }
}
void test()
{
printf("%d/n", factorial!(4)); //输出24
}
元模板
此处引出的问题是:为一个单一整数类型创建一个typedef,在大小上至少都为n位。
在C++中,无法基于模板参数的表达式结果进行条件编译,因此,所有的控制流程皆遵循模板参数的模式匹配,而不是多样的显式特定实现,更糟的是,也不能基于如“小于或等于”之类的关系进行模板的具体实现,所以在以下例子中使用了一种技巧,在其中模板可递归展开,并且每次都会递增模板值,直到满足某一特定条件。如果找不到匹配,会产生编译器堆栈递归溢出或内部错误,此时,最好的情况充其量也是一个奇怪的语法错误。此处需要一个预处理宏,以弥补缺少模板typedef。
#include <limits.h>
template< int nbits > struct Integer
{
typedef Integer< nbits + 1 > :: int_type int_type ;
} ;
struct Integer< 8 >
{
typedef signed char int_type ;
} ;
struct Integer< 16 >
{
typedef short int_type ;
} ;
struct Integer< 32 >
{
typedef int int_type ;
} ;
struct Integer< 64 >
{
typedef long long int_type ;
} ;
//如果不支持所需的大小,那么“元”程序将会递增计数器,
//直到发生内部错误,或到达INT_MAX。
//而INT_MAX的具体实现并没有定义一个int_type,
//所以终会产生一个编译错误。
struct Integer< INT_MAX >
{
} ;
#define Integer( nbits ) Integer< nbits > :: int_type
#include <stdio.h>
int main()
{
Integer( 8 ) i ;
Integer( 16 ) j ;
Integer( 29 ) k ;
Integer( 64 ) l ;
printf("%d %d %d %d/n", sizeof(i), sizeof(j), sizeof(k), sizeof(l));
return 0 ;
}
如果采用C++ Boost库,代码如下:
#include <boost/mpl/if.hpp>
#include <boost/mpl/assert.hpp>
template <int nbits> struct Integer
: mpl::if_c<(nbits <= 8), signed char
, mpl::if_c<(nbits <= 16), short
, mpl::if_c<(nbits <= 32), long
, long long>::type >::type >
{
BOOST_MPL_ASSERT_RELATION(nbits, <=, 64);
}
#include <stdio.h>
int main()
{
Integer< 8 > i ;
Integer< 16 > j ;
Integer< 29 > k ;
Integer< 64 > l ;
printf("%d %d %d %d/n", sizeof(i), sizeof(j), sizeof(k), sizeof(l));
return 0 ;
}
上述代码如果使用D语言编写,仍可使用递归模板,但有一个更好的方法,与C++不同的是,此方法非常容易看出具体的思路,并且可快速通过编译,如果未通过,还可给出一个切合实现的编译时错误信息。
template Integer(int nbits)
{
static if (nbits <= 8)
alias byte Integer;
else static if (nbits <= 16)
alias short Integer;
else static if (nbits <= 32)
alias int Integer;
else static if (nbits <= 64)
alias long Integer;
else
static assert(0);
}
int main()
{
Integer!(8) i ;
Integer!(16) j ;
Integer!(29) k ;
Integer!(64) l ;
printf("%d %d %d %d/n",
i.sizeof, j.sizeof, k.sizeof, l.sizeof);
return 0;
}
类型特性
类型特性是在编译时找出类型属性的另一个术语。
有一个用C++编写的如下例子,其判断模板参数类型是否为一个函数:
template<typename T> class IsFunctionT
{
private:
typedef char One;
typedef struct { char a[2]; } Two;
template<typename U> static One test(...);
template<typename U> static Two test(U (*)[1]);
public:
enum { Yes = sizeof(IsFunctionT<T>::test<T>(0)) == 1 };
};
void test()
{
typedef int (fp)(int);
assert(IsFunctionT<fp>::Yes == 1);
}
此模板依赖于SFINAE(Substitution Failure Is Not An Error 置换失败不是一个错误),但它的工作原理仍旧是一个高深的模板话题。
在D语言中,无须借助于模板参数模式匹配也能实现SFINAE:
template IsFunctionT(T)
{
static if ( is(T[]) )
const int IsFunctionT = 1;
else
const int IsFunctionT = 0;
}
void test()
{
typedef int fp(int);
assert(IsFunctionT!(fp) == 1);
}
上述的原理基于,如果一个类型是一个函数,那它根本不需要一个模板,也不需要试图创建一个非法的函数数组类型,并可通过表达式直接对它进行测试:
void test()
{
typedef int fp(int);
assert( is(fp == function) );
}
看到这里,你是不是对D语言也有了一个大致的认识呢,这种新语言的魅力到底有多大呢,那还等什么,赶快动手啊,D语言的美好前景就等着你去探索呢。