原来模板章仅包含(函数,结构,类)模板和类型模板参数.
快速语法.D模板,易定义和使用.更可读,很强大.定义函数,结构,类的模板,只需要提供模板参数列表.

T twice(T)(T value) {
    return 2 * value;
}

class Fraction(T) {
    T numerator;
    T denominator;

    // ...
}

上面为快速语法,上面的完整语法如下,加个模板关键字:

template twice(T) {
    T twice(T value) {
        return 2 * value;
    }
}

template Fraction(T) {
    class Fraction {
        T numerator;
        T denominator;

        // ...
    }
}

完整语法,编译器经常用,可以想见包装步骤:
1,用模板块包装定义
2,给块相同名
3,移动模板参数列到模板块.
这样就变为完整语法.程序员可自己显式定义同名模板.
在模板块中可不只一个定义:

template MyTemplate(T) {
    T foo(T value) {
        return value / 3;
    }

    struct S {
        T member;
    }
}

实例化一个模板块,会实例化所有块里面东西.

    auto result = MyTemplate!int.foo(42);
    writeln(result);

    auto s = MyTemplate!double.S(5.6);
    writeln(s.member);

特定的实例化引入名字空间.模板名字太长,最好用别名,如:

    alias MyStruct = MyTemplate!dchar.S;

// ...

    auto o = MyStruct('a');
    writeln(o.member);

同名模板就是模板块中有与自己名字一样的定义.
快速模板符号就是同名模板的快捷方式.

template isTooLarge(T) {
    enum isTooLarge = T.sizeof > 20;
    //定义类型的大小
}

这样用writeln(isTooLarge!int);.只一级.
也可以这样定义enum isTooLarge(T) = T.sizeof > 20;

同名模板还可以这样:

template LargerOf(A, B) {
    static if (A.sizeof < B.sizeof) {
        alias LargerOf = B;
    } else {//选择大的.
        alias LargerOf = A;
    }
}

LargerOf!(int, long)这样使用.避免了再加一个名字的尴尬
这在模板参数自身也是模板依赖模板参数时很有用.

auto calculate(A, B)(A a, B b) {
    LargerOf!(A, B) result;//依赖
    // ...
    return result;
}

void main() {
    auto f = calculate(1, 2L);
    static assert(is (typeof(f) == long));
}

模板类型:函数/类/结构模板,成员函数模板,如:

class Sink {
    string content;

    void put(T)(auto ref const T value) {//模板
        import std.conv;
        content ~= value.to!string;
    }
}

模板不能为(太多),也不能为抽象.

class Sink {
    string content;

    void put(T)(auto ref const T value) {
        import std.conv;
        content ~= value.to!string;
    }
}

class SpecialSink : Sink {//模板是不能为虚的
    void put(T)(auto ref const T value) {
        import std.string;
        super.put(format("{%s}", value));
    }//重载吗?不,隐藏!
}

void fillSink(Sink sink) {
    sink.put(42);//分发至父类.
    sink.put("hello");//都分发至父类了
    //函数非虚,模板非虚
}

void main() {
    auto sink = new SpecialSink();
    fillSink(sink);

    import std.stdio;
    writeln(sink.content);//父类
}

联合模板:

union SegmentedValue(ActualT, SegmentT) {
    ActualT value;
    SegmentT[/*段数*/] segments;
}//指定值型,段型

必须在为两个特殊类型实例化的编译时计算段数.
这样:

enum segmentCount(A,S) =((A.sizeof + (S.sizeof - 1))/ S.sizeof);
//现在这样定义
union SegmentedValue(ActualT, SegmentT) {
    ActualT value;
    SegmentT[segmentCount!(ActualT, SegmentT)] segments;
}
//等价于前文的例子
import std.stdio;

void main() {
    auto address = SegmentedValue!(uint, ubyte)(0xc0a80102);

    foreach (octet; address.segments) {
        write(octet, ' ');
    }
}

有了模板,想重定义,非常简单,一句话的事:

  auto address = SegmentedValue!(uint, ushort)(0xc0a80102);

接口模板,方便使用接口

interface ColoredObject(ColorT) {
    void paint(ColorT color);
}
//必须有上面的函数,就像转了一个弯一样
struct RGB {
    ubyte red;
    ubyte green;
    ubyte blue;
}

class PageFrame : ColoredObject!RGB {
    void paint(RGB color) {
    //你可以用你想画的颜色类型
        // ...
    }
}
//
alias Frequency = double;

class Bulb : ColoredObject!Frequency {
    void paint(Frequency color) {
        //用自已合适的类型来画
        // ...
    }
}

模板的每个实例的类型都是不同的.
模板参数还可以为值,this,别名,元组.
以前的都是类型模板参数.还可以有值模板函数.
值模板参数给了在实现模板时使用特定值的灵活性.
模板是编译时,所以给定的值也必须是编译时已知的值.
展示几何形状:

struct Polygon(size_t N) {
    Point[N] corners;
// ...
}
    auto centagon = Polygon!100();

定义多边形.
值类型可以为:基本类型,结构,数组,串等等.只要是编译时知道的,都可以.就是c++的函数作为模板参数一样.

struct S {
    int i;
}

void foo(S s)() {//值模板参数
    // ...
}

void main() {
    foo!(S(42))();    // 用S(42)字面量初化
}

以串作为模板

import std.string;

class XmlElement(string tag) {
    double value;

    this(double value) {
        this.value = value;
    }

    override string toString() const {
        return format("<%s>%s</%s>", tag, value, tag);
    }//继承
}

可以这样:

alias Location = XmlElement!"location";
alias Temperature = XmlElement!"temperature";
alias Weight = XmlElement!"weight";

void main() {
    Object[] elements;//用这个最基本的

    elements ~= new Location(1);
    elements ~= new Temperature(23);
    elements ~= new Weight(78);

    writeln(elements);
}

默认值:

struct Point(T, size_t dimension = 3) {
    T[dimension] coordinates;
}
    Point!double center;   
    Point!(int, 2) point;

有些特殊值就应该作为编译期值,比如那些特征值.

import std.stdio;

void func(T,
          string functionName = __FUNCTION__,
          string file = __FILE__,
          size_t line = __LINE__)(T parameter) {
    writefln("在哪%s 在文件%s,行 %s.",functionName, file, line);
}

void main() {
    func(42);    //出现在此,在此实例化.行数为在此的行数
}

上面是特殊关键字作为默认模板参数.有些是没必要的
特殊关键字在他们是否出现在内部代码或作为默认参数表现是不同的.
类似,作为默认模板参数,指向在哪实例化模板,而不是关键字出现的地方.
将在下面的多维数组重载的示例中使用__FUNCTION__.
this作为模板参数.
不像其他模板,成员函数模板可以有this作为参数.
此时在(this B)中的Bthis所指对象的具体类型.

struct MyStruct(T) {
    void foo(this OwnType)() const {
        writeln("本对象类型: ", OwnType.stringof);
    }
}
//使用
    auto m = MyStruct!int();
    auto c = const(MyStruct!int)();
    auto i = immutable(MyStruct!int)();

    m.foo();
    c.foo();
    i.foo();
//输出:
本对象类型: MyStruct!int
本对象类型: const(MyStruct!int)
本对象类型: immutable(MyStruct!int)

的类型还包括类型限定符.
this也可出现在非模板类型的成员函数模板参数中,其在模板插件中很有用.
alias模板参数.与任何符号/程序中用的表达式有关.只是模板参数必须与在模板中的用法匹配.
过滤映射使用别名来决定他们要执行的操作.
我们来看一个简单修改存在变量的模板.

struct MyStruct(alias variable) {
    void set(int value) {
        variable = value;
    }
}

使用别名作参数.

    int x = 1;
    int y = 2;

    auto object = MyStruct!x();//感觉是多此一举
    object.set(10);
    writeln("x: ", x, ", y: ", y);

在实例化模板时必须指定这些参数.
现在是调用:

void caller(alias func)() {
    write("calling: ");
    func();
}

类似c++的函数作为模板参数.
只要模板参数能够匹配其用法,就可以实例化,但如不匹配,则编译错误.

    int variable;
    caller!variable();  //编译错误,表示

编译错误,表示模板参数不能匹配模板用法.

class C {
    void opCall() {
        writeln("C.opCall called.");
    }//重载()
}

// ...

    auto o = new C();
    caller!o();//函数子
    caller!({ writeln("λ."); })();

也可以限制别名模板.

import std.stdio;

void foo(alias variable)() {
    writefln("The general definition is using '%s' of type %s.",
             variable.stringof, typeof(variable).stringof);
}

void foo(alias int i)() {
    writefln("The int specialization is using '%s'.",
             i.stringof);
}

void foo(alias double d)() {
    writefln("The double specialization is using '%s'.",
             d.stringof);
}

void main() {
    string name;
    foo!name();

    int count;
    foo!count();

    double length;
    foo!length();
}

别名参数名间加限定符.
别名参数使模板内实际变量名可用.
元组模板参数.

void info(T...)(T args) {
    foreach (i, arg; args) {
        writefln("%s: %s argument %s",
                 i, typeof(arg).stringof, arg);
    }
}//类似打印,简单打印

T与args都是元组.T表参数类型,args表参数们

import std.stdio;

// ...

void main() {
    info(1, "abc", 2.3);//三种模板
}

typeof(arg),值的类型.也可用T[i]得到,
显式指定模板参数,std.conv.to中的to!string(42);
模板参数可为值,类型或其他的混合.
要求能决定每个模板参数是否是类型,模板然后相应编码.
可以将参数当作AliasSeq
相当于反射了

import std.stdio;

void main() {
    writeln(structDefinition!("Student",
                              string, "name",
                              int, "id",
                              int[], "grades")());
}

根据源码产生结构串,如下:

struct Student {
    string name;
    int id;
    int[] grades;
}

先生成串,再插件它.下面是实现,注意函数如何利用is表达式,在is(参)有效时为真.

import std.string;

string structDefinition(string name, Members...)() {
    static assert((Members.length % 2) == 0,"类型/名要成对.");
    string result="struct "~name~ "\n{\n";//开始
    foreach (i, arg; Members) {
        static if (i % 2) {//奇数为名,确保为串
            static assert(is (typeof(arg) == string), "成员名" ~ arg.stringof ~ "不是串.");
        } else {//类型
            static assert(is (arg),arg.stringof ~ " 不是类型.");
            result ~= format("    %s %s;\n", Members[i].stringof, Members[i+1]);
        }//0为类型,1为名,先为else,
    }
    result ~= "}";//尾
    return result;
}

import std.stdio;

void main() {
    writeln(structDefinition!("Student",
                              string, "name",
                              int, "id",
                              int[], "grades")());
}

继续模板,有些很难拼,在模板和非模板代码中都可用
typeof(this), typeof(super), 和 typeof(return).
生成

struct List(T) {//即对象的类型
    typeof(this) *next;//相当于List!T,
    // ...
}

typeof(this),生成this(引用)的类型.在构/类/成员函数外中都可用

class ListImpl(T) {
    // ...
}

class List(T) : ListImpl!T {
    // The type of 'next' is ListImpl!int when T is int
    typeof(super) *next;
    // ...
}

typeof(super)生成基类(父类)类型.
typeof(return)生成函数返回类型

LargerOf!(A, B) calculate(A, B)(A a, B b) {
//不用`auto`,至少可以消除函数注释
    // ...,用这个(typeof(return))
    typeof(return) result;//类型为A/B
    // ...
    return result;
}

模板限制:
就像类型模板,模板参数都是可以限制的:

void foo(int value)() {
    // ... 生成定义...
}

void foo(int value : 0)() {
    // ... 为0时的特殊情况 ...
}//
可在元编程中利用它.
元编程

是关于生成代码的,因而模板是d的高级特征之一.
模板是生成代码的,只要是写生成代码的就叫元编程.
模板是编译时特征,模板实例化时一些运行时计算可转移至编译时,
ctfe是编译时执行函数(D的又一高级特征).
编译时计算通常是基于递归实例化模板

int sum(int last)() {
    return last + sum!(last - 1)();
}
int sum(int last : 0)() {//特化
    return 0;
}

注意这样是不行的

int sum(int last)() {//因为(?:)是运行时执行,
    return (last == 0
            ? last
            : last + sum!(last - 1)());
}//编译时没有递归边界检查,所以编译错误

测试代码:

import std.stdio;

void main() {
    writeln(sum!4());//编译时执行,==writeln(10);
}

元编程的好处:将运行时执行操作转移至编译时.
ctfe消除了一些d编程的习语.

编译时多态

面向对象编程,多态是依靠继承获得的.
如下:

import std.stdio;

interface SoundEmitter {
    string emitSound();
}

class Violin : SoundEmitter {
    string emitSound() {
        return "kaka";
    }
}

class Bell : SoundEmitter {
    string emitSound() {
        return "ding";
    }
}

void useSoundEmittingObject(SoundEmitter object) {
    // ... some operations ...
    writeln(object.emitSound());
    // ... more operations ...
}

void main() {
    useSoundEmittingObject(new Violin);
    useSoundEmittingObject(new Bell);
}

作为编译时特征,模板提供的多态叫编译时多态.oop叫运行时多态
运行时要求都有个接口.
编译时要求使用类型与模板的用法匹配.
只要能编译代码.模板参数就可用该模板.当然,参数还得满足模板限制.

void useSoundEmittingObject(T)(T object) {
    // ... some operations ...
    writeln(object.emitSound());
    // ... more operations ...
}

class Car {
    string emitSound() {
        return "honk honk";
    }
}

// ...

    useSoundEmittingObject(new Violin);
    useSoundEmittingObject(new Bell);
    useSoundEmittingObject(new Car);//汽车

编译时多态无继承关系,也都可以编译,都有效.所以也叫鸭子类型,强调行为,而不是具体的类型.
模板很容易膨胀代码,这是模板的一个大缺点.
模板限制,模板是编译时,因而模板限制也必须编译时求值.is(...)就很有用了.

template myTemplate(T...)
        if (T.length == 1) {//单参
    static if (is (T[0])) {//是类型
        enum bool myTemplate = /* ... */;
    } else {//其他
        enum bool myTemplate = /* ... */;
    }
}
可以看看std.traits.
命名限制

类似c++的概念,限制太复杂时.

void use(T)(T object) {
    // ...
    object.prepare();
    // ...
    object.fly(42);
    // ...
    object.land();
    // ...
}

结合了D匿名函数,typeof,is表达式,同名模板

template canFlyAndLand(T) {
    enum canFlyAndLand = is (typeof(
    {
        T object;
        object.prepare();  // should be preparable for flight
        object.fly(1);     // should be flyable for a certain distance
        object.land();     // should be landable
    }()));
}

用λ,然后执行.同名模板函数,
λ后的()用于求值.调用语法在typeof里面,所以不会执行.typeof产生的类型,它不执行表达式,如能执行,其产生表达式的类型.

    int i = 42;
    typeof(++i) j;    // =='int j;'

    assert(i == 42);  // 未执行++i 

如果typeof返回的类型不是有效类型,则不返回类型,连void都不是.所以is(typeof(...))为假.
因而如能编译,则产生有效类型.
is(T),如T是有效类型,则为,否则为.

    int i;
    writeln(is (typeof(i)));                  // true
    writeln(is (typeof(nonexistentSymbol)));  // false

虽然未能编译,但编译器不报错.这样(typeof(不存在))就没有产生类型,所以is(T)就为假.
然后就可以:

void use(T)(T object)
        if (canFlyAndLand!T) {
    // ...
}

这就是c++的概念啊.

class ModelAirplane {//满足
    void prepare() {
    }

    void fly(int distance) {
    }

    void land() {
    }
}

class Pigeon {//不满足
    void fly(int distance) {//
    }
}

// ...

    use(new ModelAirplane);  //编译
    use(new Pigeon);        //编译错误

无论是否命名,因为模板有限制,编译器在使用模板处而不是实现的地方报错.
在多维运算符重载中使用模板
多维操作符重载时,opIndexopSlice的职责.
opDollar, opIndex, 和 opSlice用于索引切片元素.

    size_t opDollar(size_t dimension)() const {
        // ...
    }
    Tuple!(size_t, size_t) opSlice(size_t dimension)(size_t begin,size_t end) {
        return tuple(begin, end);
    }
    Range opIndex(A...)(A arguments) {
        // ...
    }
//索引

opIndexAssign 和 opIndexOpAssign也有模板版本,在集合的元素区间操作.

    m[a, b..c, $-1, d..e] = 42;//用户定义赋值操作

多维索引与切片,相当于

    m.opIndexAssign(42,a,m.opSlice!1(b, c),m.opDollar!2() - 1,m.opSlice!3(d, e));    
//1维维的操作

opIndexAssign,决定参数的元素区间.
示例.演示二维类型重载.当然可更高效的用二维来代替本次构建单元素子矩阵.
writeln(__FUNCTION__)帮助我们提示谁在干活.由模板限制保证维度值的正确.

import std.stdio;
import std.format;
import std.string;

/* 二维整数组. */
struct Matrix {
private:

    int[][] rows;

    /* 代表行列区间. */
    struct Range {
        size_t begin;size_t end;
    }

    /* 回行列区间指定的子矩阵.*/
    Matrix subMatrix(Range rowRange, Range columnRange) {
        writeln(__FUNCTION__);
        int[][] slices;
        foreach (row; rows[rowRange.begin .. rowRange.end]) {
            slices ~= row[columnRange.begin .. columnRange.end];
        }

        return Matrix(slices);
    }

public:

    this(size_t height, size_t width) {
        writeln(__FUNCTION__);
        rows = new int[][](height, width);
    }

    this(int[][] rows) {
        writeln(__FUNCTION__);
        this.rows = rows;
    }

    void toString(void delegate(const(char)[]) sink) const {
        sink.formattedWrite!"%(%(%5s %)\n%)"(rows);
    }

    /* 矩阵的每一元素赋值.*/
    Matrix opAssign(int value) {
        writeln(__FUNCTION__);

        foreach (row; rows) {
            row[] = value;
        }

        return this;
    }

    //对每一元素用二元操作符赋值
    Matrix opOpAssign(string op)(int value) {
        writeln(__FUNCTION__);

        foreach (row; rows) {
            mixin ("row[] " ~ op ~ "= value;");
        }

        return this;
    }

    /* 返回特定维长度. */
    size_t opDollar(size_t dimension)() const
            if (dimension <= 1) {
        writeln(__FUNCTION__);

        static if (dimension == 0) {
            /* 0维长度是`行`数组长度. */
            return rows.length;

        } else {//1维为`行`元素的长度
            return rows.length ? rows[0].length : 0;
        }
    }

    /* 返回[头..尾]的区间,尽管在此未用`维度`模板参数,其在其他类型是有用的. */
    Range opSlice(size_t dimension)(size_t begin, size_t end)
            if (dimension <= 1) {
        writeln(__FUNCTION__);

        return Range(begin, end);
    }

    /* 返回参数定义的子矩阵. */
    Matrix opIndex(A...)(A arguments)
            if (A.length <= 2) {
        writeln(__FUNCTION__);

        /* 我们从表示整个矩阵的区间开始,这样无参的`opIndex`表示`所有元素`. */
        Range[2] ranges = [ Range(0, opDollar!0),
                            Range(0, opDollar!1) ];
        foreach (dimension, a; arguments) {
            static if (is (typeof(a) == Range)) {
                /* 这个维度已经指定为像'matrix[begin..end]这样的区间',就不变. */
                ranges[dimension] = a;

            } else static if (is (typeof(a) : size_t)) {
                /* 这个维度像我们想要按单元素区间指定的'matrix[i]'指定. */
                ranges[dimension] = Range(a, a + 1);

            } else {
                /* 不再希望其他类型. */
                static assert(false, format("Invalid index type: %s",typeof(a).stringof));
            }
        }

        /* 返回按参数指定的子矩阵. */
        return subMatrix(ranges[0], ranges[1]);
    }

    /* 给每个子矩阵的元素赋值. */
    Matrix opIndexAssign(A...)(int value, A arguments)if (A.length <= 2) {
        writeln(__FUNCTION__);

        Matrix subMatrix = opIndex(arguments);
        return subMatrix = value;
    }

    //用子矩阵的每个元素和一个二元操作值并赋值回那个元素
    Matrix opIndexOpAssign(string op, A...)(int value,A arguments)if (A.length <= 2) {
        writeln(__FUNCTION__);

        Matrix subMatrix = opIndex(arguments);
        mixin ("return subMatrix " ~ op ~ "= value;");
    }
}

/* 执行串指定的表达式,打印结果及新矩阵的状态.*/
void execute(string expression)(Matrix m) {
    writefln("\n--- %s ---", expression);
    mixin ("auto result = " ~ expression ~ ";");
    writefln("result:\n%s", result);
    writefln("m:\n%s", m);
}

void main() {
    enum height = 10;
    enum width = 8;

    auto m = Matrix(height, width);

    int counter = 0;
    foreach (row; 0 .. height) {
        foreach (column; 0 .. width) {
            writefln("Initializing %s of %s",
                     counter + 1, height * width);

            m[row, column] = counter;
            ++counter;
        }
    }

    writeln(m);

    execute!("m[1, 1] = 42")(m);
    execute!("m[0, 1 .. $] = 43")(m);
    execute!("m[0 .. $, 3] = 44")(m);
    execute!("m[$-4 .. $-1, $-4 .. $-1] = 7")(m);

    execute!("m[1, 1] *= 2")(m);
    execute!("m[0, 1 .. $] *= 4")(m);
    execute!("m[0 .. $, 0] *= 10")(m);
    execute!("m[$-4 .. $-2, $-4 .. $-2] -= 666")(m);

    execute!("m[1, 1]")(m);
    execute!("m[2, 0 .. $]")(m);
    execute!("m[0 .. $, 2]")(m);
    execute!("m[0 .. $ / 2, 0 .. $ / 2]")(m);

    execute!("++m[1..3, 1..3]")(m);
    execute!("--m[2..5, 2..5]")(m);

    execute!("m[]")(m);
    execute!("m[] = 20")(m);
    execute!("m[] /= 4")(m);
    execute!("(m[] += 5) /= 10")(m);
}