可变头模式

原文地址

可变头结构模式

今天,用户定义类型仍是D中的二等公民.如不支持(头部可变性,可在不改变引用值时,改变头).本文的可变头模式用来支持用户定义类型.

介绍

D有一等,二等公民.一等公民有数组和指针,他们可隐式头可变.如,const(T[])可隐式转换为const(T)[].
D有多种转换方法:别名 本,构造器,分发操作,转换操作,及子类化.指针和动态数组退化为它们的可变头的方式与前面不一样,要模仿,就要定义新的转换类型.

更改编译器和语言太麻烦.且隐式转换很难懂.但有时很方便,可试试下面方法.

Unqual

目前可用std.traits.Unqual.有时有用,它去掉const,immutable,inout,和shared.但对某些类型,则不行.

struct S(T) {
    T[] arr;
}

用Unqual,无法编译:

void foo(T)(T a) {
    Unqual!T b = a; //不能隐式转换immutable(S!int) 到 S!int
}

unittest {
    immutable s = S!int([1,2,3]);
    foo(s);//失败
}

转换失败,表明有可能转换,只是失败了.
然后老手出马了.

void bar(T)(T a) {
    Unqual!T b = cast(Unqual!T)a;
    b.arr[0] = 4;
}

unittest {
    immutable s = S!int([1,2,3]);
    bar(s);
    assert(s.arr[0] == 1); //失败,bar()改了
}//将不变转成可变了.

如果用int[]而不是S!int,则第一个编译,第二个编译不过.但,由于S!int是用户定义类型,使我们必须编写一个该支持不支持,运行时搞鬼的模板函数.

可变头()

我们可以做得更好.D可获取本 动态引用的const或immutable状态:

struct S {
    void foo(this T)() {
        import std.stdio : writeln;
        writeln(T.stringof);//
    }
}
unittest {
    S s1;
    const S s2;
    s1.foo(); // 打印"S".
    s2.foo(); // 打印"const(S)".
}

现在,我们可用此信息定义方法来创建正确的可变头:

struct S(T) {
    T[] arr;
    auto 可变头(this This)() const {
        import std.traits : CopyTypeQualifiers;
        return S!(CopyTypeQualifiers!(This, T))(arr);
    }//修改的是`T`,即数组中的`元素`
}
unittest {
    const a = S!int([1,2,3]);
    auto b = a.可变头();
    assert(is(typeof(b) == S!(const int)));
    //现在类型为常
    assert(a.arr is b.arr);//相同数组,无复制
    b.arr[0] = 3; //正确,编译失败了,不能修改常式
}

借助统调,我们还可为内置类型定义可变头():

auto 可变头(T)(T value) {
    import std.traits;
    import std.typecons : rebindable;
    static if (isPointer!T) {
        return value;// T是指针/数组,自然退化
    } else static if (isDynamicArray!T) {
        return value;//T是动态数组,自然退化
    } else static if (!hasAliasing!(Unqual!T)) {
        return cast(Unqual!T)value;
        // T是`旧类型`(内置类型,仅内置类型的构)
    } else static if (is(T == class)) {
        //引用类型类,仅引用可搞成`可变头`
        return rebindable(value);
    } else static if (isAssociativeArray!T) {
        // 关联数组是引用,同上
        return rebindable(value);
    } else {
        static assert(false, "不能绑定"~T.stringof~"类型");
    }
}
unittest {
    const(int*[3]) a = [null, null, null];
    auto b = a.可变头();
    assert(is(typeof(b) == const(int)*[3]));
}

尾常是指柄为常const(int) *,const(int[]),const(immutable(int)[])* 及串.
可变头下层内容(不变/变),上层(直接成员)可变const(int)[], int * , string, and Rebindable!MyClass,及普通int, float 及 struct S { int n; }
现在,每当需要用可变头变量指向尾常时,就可简单在需要数据上调用可变头()就行了.Unqual!T可能会丢弃重要的类型信息,并且不提供错误消息.而可变头()则相反.现在取可变头类型.

import std.traits : ReturnType;
alias 可变头(T) = ReturnType!((T t) => t.可变头());//注意:一个小写,一个大写.

可变头第一时间给出错误信息.
常见需要是保留类型的尾常(不变)属性.
可用std.traits.CopyTypeQualifiers定义一个传播常/不变可变头模板.

import std.traits : CopyTypeQualifiers;
alias 可变头(T,常源) = 可变头!(CopyTypeQualifiers!(常源,T));//类似c++的`用`,

这样,(常|不变)(MyStruct!int)变成了MyStruct!((常|不变)int).

示例

简化了代码.

import std.range;

//检查可变头!R,就是区间
auto map(alias Fn, R)(R range) if (isInputRange!(可变头!R)) {
    //`可变头!R`及range.可变头(),用可变头的扩展
    return MapResult!(Fn, 可变头!R)(range.可变头());//加了个`映射结果`
}
struct MapResult(alias Fn, R) {//区间
    R range;
    this(R _range) {range = _range;}
    void popFront() {range.popFront();}
    @property
    auto ref front() {
        return Fn(range.front);
    }
    
    @property
    bool empty() {
        return range.empty;
    }
    
    static if (isBidirectionalRange!R) {
        @property
        auto ref back() {
            return Fn(range.back);
        }

        void popBack() {
            range.popBack();
        }
    }

    static if (hasLength!R) {
        @property
        auto length() {
            return range.length;
        }
        alias opDollar = length;//$
    }

    static if (isRandomAccessRange!R) {
        auto ref opIndex(size_t idx) {
            return Fn(range[idx]);
        }
    }

    static if (isForwardRange!R) {
        @property
        auto save() {
            return MapResult(range.save);
        }
    }
    
    static if (hasSlicing!R) {
        auto opSlice(size_t from, size_t to) {
            return MapResult(range[from..to]);
        }
    }
    
    //上面是基础,还要实现可变头().
    auto 可变头(this This)() const {
        alias R=MapResult!(Fn, 可变头!(R, This));
        return R(range.可变头());
    }//这个是重点
}

auto equal(R1, R2)(R1 r1, R2 r2) if (isInputRange!(可变头!R1) && isInputRange!(可变头!R2)) {
    //可变头版本
    auto _r1 = r1.可变头();
    auto _r2 = r2.可变头();
    while (!_r1.empty && !_r2.empty) {
        if (_r1.front != _r2.front) return false;
        _r1.popFront();
        _r2.popFront();
    }
    return _r1.empty && _r2.empty;
}

unittest {
    //用户代码都没有`可变头`
    const arr = [1,2,3];
    const squares = arr.map!(a => a*a);
    const squaresPlusTwo = squares.map!(a => a+2);
    assert(equal(squaresPlusTwo, [3, 6, 11]));
}

当前标准库完全可创建常版本映射结果,但无法迭代,因而失败.
一般是const(int[])转const(int)[].也不仅仅是区间,在引用计数中也有用RefCounted!T: const(RefCounted!(T))应转换为 RefCounted!(const(T))
不变灵针可变图中可能有用.

posted @ 2020-06-26 11:35  zjh6  阅读(14)  评论(0)    收藏  举报  来源