Head-mutable implementation for D
import std.traits; | |
import std.typecons : rebindable, Rebindable; | |
alias HeadMutable(T) = ReturnType!((T t) => t.headMutable()); | |
alias HeadMutable(T, ConstSource) = HeadMutable!(CopyTypeQualifiers!(ConstSource, T)); | |
auto headMutable(T)(T value) { | |
static if (isPointer!T) { | |
// T is a pointer, and decays naturally. | |
return value; | |
} else static if (isDynamicArray!T) { | |
// T is a dynamic array, and decays naturally. | |
return value; | |
} else static if (!hasAliasing!(Unqual!T)) { | |
// T is a POD datatype - either a built-in type, or a struct with only POD members. | |
return cast(Unqual!T)value; | |
} else static if (isStaticArray!T && is(HeadMutable!(ElementType!T) == ElementType!(Unqual!T))) { | |
// T is a static array, and can safely be cast to Unqual!T. | |
// Can't cast directly to HeadMutable!(ElementType!T)[N], since ElementType!T.headMutable() | |
// may change the layout of the type. | |
return cast(Unqual!T)value; | |
} else static if (is(T == class)) { | |
// Classes are reference types, so only the reference to it may be made head-mutable. | |
return rebindable(value); | |
} else static if (isAssociativeArray!T) { | |
// AAs are reference types, so only the reference to it may be made head-mutable. | |
return rebindable(value); | |
} else { | |
static assert(false, "Type `"~T.stringof~"` cannot be made head-mutable."); | |
} | |
} | |
unittest | |
{ | |
// Regular types: | |
static assert(is(HeadMutable!(int) == int)); | |
static assert(is(HeadMutable!(int*) == int*)); | |
static assert(is(HeadMutable!(int[]) == int[])); | |
static assert(is(HeadMutable!(int[10]) == int[10])); | |
static assert(is(HeadMutable!(int*[10]) == int*[10])); | |
static assert(is(HeadMutable!(int[int]) == Rebindable!(int[int]))); | |
static assert(is(HeadMutable!(const int) == int)); | |
static assert(is(HeadMutable!(const int*) == const(int)*)); | |
static assert(is(HeadMutable!(const int[]) == const(int)[])); | |
static assert(is(HeadMutable!(const int[10]) == int[10])); | |
static assert(is(HeadMutable!(const int*[10]) == const(int)*[10])); | |
static assert(is(HeadMutable!(const int[int]) == Rebindable!(const(int[int])))); | |
static assert(is(HeadMutable!(immutable int) == int)); | |
static assert(is(HeadMutable!(immutable int*) == immutable(int)*)); | |
static assert(is(HeadMutable!(immutable int[]) == immutable(int)[])); | |
static assert(is(HeadMutable!(immutable int[10]) == int[10])); | |
static assert(is(HeadMutable!(immutable int*[10]) == immutable(int)*[10])); | |
static assert(is(HeadMutable!(immutable int[int]) == Rebindable!(immutable(int[int])))); | |
// Class: | |
class C1 { } | |
static assert(is(HeadMutable!C1 == C1)); | |
static assert(is(HeadMutable!(const C1) == Rebindable!(const C1))); | |
static assert(is(HeadMutable!(immutable C1) == Rebindable!(immutable C1))); | |
// Struct without aliasing: | |
static struct S1 { | |
int n; | |
immutable(int)[] arr; | |
} | |
static assert(is(HeadMutable!S1 == S1)); | |
static assert(is(HeadMutable!(const S1) == S1)); | |
static assert(is(HeadMutable!(immutable S1) == S1)); | |
// Struct with aliasing has no head-mutable analog: | |
static struct S2 { | |
int[] arr; | |
} | |
// Should be possible to create head-mutable version of all-mutable struct. | |
//static assert(is(HeadMutable!S2)); | |
static assert(!is(HeadMutable!(const S2))); | |
static assert(!is(HeadMutable!(immutable S2))); | |
// Struct with aliasing, with headMutable hook: | |
static struct S3 { | |
int[] arr; | |
S3 headMutable(this T)() { | |
return S3(arr.dup); | |
} | |
} | |
static assert(is(HeadMutable!S3 == S3)); | |
static assert(is(HeadMutable!(const S3) == S3)); | |
static assert(is(HeadMutable!(immutable S3) == S3)); | |
// Templated struct with aliasing, with headMutable hook | |
struct S4(T) { | |
T[] arr; | |
auto headMutable(this This)() const { | |
alias HeadMutableS4 = S4!(CopyTypeQualifiers!(This, T)); | |
return HeadMutableS4(arr.dup); | |
} | |
} | |
static assert(is(HeadMutable!(S4!int) == S4!int)); | |
static assert(is(HeadMutable!(const S4!int) == S4!(const int))); | |
static assert(is(HeadMutable!(immutable S4!int) == S4!(immutable int))); | |
} | |
unittest | |
{ | |
{ | |
// No copying of values. | |
const(int)[] a = [1,2,3]; | |
auto b = a.headMutable(); | |
assert(a is b); | |
} | |
{ | |
// No copying of values. | |
immutable(int)[] a = [1,2,3]; | |
auto b = a.headMutable(); | |
assert(a is b); | |
} | |
{ | |
// No copying of values. | |
const(int[]) a = [1,2,3]; | |
auto b = a.headMutable(); | |
assert(a is b); | |
} | |
{ | |
// No copying of values. | |
immutable(int[]) a = [1,2,3]; | |
auto b = a.headMutable(); | |
assert(a is b); | |
} | |
{ | |
// No copying of values unless the headMutable() member function says so. | |
struct S4(T) { | |
T[] arr; | |
auto headMutable(this This)() const { | |
return S4!(CopyTypeQualifiers!(This, T))(arr); | |
} | |
} | |
const S4!int a = S4!int([1,2,3]); | |
auto b = a.headMutable(); | |
assert(a.arr is b.arr); | |
} | |
{ | |
// No copying of values unless the headMutable() member function says so. | |
struct S5(T) { | |
T[] arr; | |
auto headMutable(this This)() const { | |
return S5!(CopyTypeQualifiers!(This, T))(arr); | |
} | |
} | |
immutable S5!int a = S5!int([1,2,3]); | |
auto b = a.headMutable(); | |
assert(a.arr is b.arr); | |
} | |
} | |
import std.range; | |
// Note that we check not if R is a range, but if HeadMutable!R is | |
auto map(alias Fn, R)(R range) if (isInputRange!(HeadMutable!R)) { | |
// Using HeadMutable!R and range.headMutable() here. | |
// This is basically the extent to which code that uses head-mutable data types will need to change. | |
return MapResult!(Fn, HeadMutable!R)(range.headMutable()); | |
} | |
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]); | |
} | |
} | |
// All the above is as you would normally write it. | |
// We also need to implement headMutable(). | |
// Generally, headMutable() will look very much like this - instantiate the same | |
// type template that defines typeof(this), use HeadMutable!(T, ConstSource) to make | |
// the right parts const or immutable, and call headMutable() on fields as we pass | |
// them to the head-mutable type. | |
auto headMutable(this This)() const { | |
alias HeadMutableMapResult = MapResult!(Fn, HeadMutable!(R, This)); | |
return HeadMutableMapResult(range.headMutable()); | |
} | |
} | |
auto equal(R1, R2)(R1 r1, R2 r2) if (isInputRange!(HeadMutable!R1) && isInputRange!(HeadMutable!R2)) { | |
// Need to get head-mutable version of the parameters to iterate over them. | |
auto _r1 = r1.headMutable(); | |
auto _r2 = r2.headMutable(); | |
while (!_r1.empty && !_r2.empty) { | |
if (_r1.front != _r2.front) return false; | |
_r1.popFront(); | |
_r2.popFront(); | |
} | |
return _r1.empty && _r2.empty; | |
} | |
unittest { | |
// User coded does not have to use headMutable at all: | |
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])); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment