d的可变头模式
可变头结构模式
今天,用户定义类型仍是D中的二等公民.如不支持(头部可变性,能够在不改变引用值的情况下,改变头).本文介绍udt模式来实现它.
介绍
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))
在不变灵针与可变图可能有用.
浙公网安备 33010602011771号