diff --git a/std/typecons.d b/std/typecons.d index bdec03e8a0c..40afe9ad606 100644 --- a/std/typecons.d +++ b/std/typecons.d @@ -1666,11 +1666,11 @@ Convenience function for creating a $(D Rebindable) using automatic type inference. Params: - obj = A reference to an object, interface, associative array, or an array slice - to initialize the `Rebindable` with. + obj = A reference to an object, interface, associative array, or an array slice. + s = Struct instance. Returns: - A newly constructed `Rebindable` initialized with the given reference. + A newly constructed `Rebindable` initialized with the given data. */ Rebindable!T rebindable(T)(T obj) if (is(T == class) || is(T == interface) || isDynamicArray!T || isAssociativeArray!T) @@ -1680,6 +1680,17 @@ Rebindable!T rebindable(T)(T obj) return ret; } +/// ditto +Rebindable!S rebindable(S)(S s) +if (is(S == struct) && !isInstanceOf!(Rebindable, S)) +{ + // workaround for rebindableS = rebindable(s) + static if (isMutable!S) + return s; + else + return Rebindable!S(s); +} + /** This function simply returns the $(D Rebindable) object passed in. It's useful in generic programming cases when a given object may be either a regular @@ -1787,6 +1798,220 @@ unittest assert(rebindable(pr3341_aa)[321] == 543); } +/** Models safe reassignment of otherwise constant struct instances. + * + * A struct with a field of reference type cannot be assigned to a constant + * struct of the same type. `Rebindable!(const S)` allows assignment to + * a `const S` while enforcing only constant access to its fields. + * + * `Rebindable!(immutable S)` does the same but field access may create a + * temporary copy of `S` in order to enforce _true immutability. + */ +template Rebindable(S) +if (is(S == struct)) +{ + static if (isMutable!S) + alias Rebindable = S; + else + struct Rebindable + { + private: + // mutPayload's pointers must be treated as tail const + void[S.sizeof] mutPayload; + + void emplace(ref S s) + { + import std.conv : emplace; + static if (__traits(compiles, () @safe {S tmp = s;})) + () @trusted {emplace!S(mutPayload, s);}(); + else + emplace!S(mutPayload, s); + } + + public: + this()(auto ref S s) + { + emplace(s); + } + + // immutable S cannot be passed to auto ref S above + static if (!is(S == immutable)) + this()(immutable S s) + { + emplace(s); + } + + void opAssign()(auto ref S s) + { + movePayload; + emplace(s); + } + + static if (!is(S == immutable)) + void opAssign()(immutable S s) + { + movePayload; + emplace(s); + } + + void opAssign(Rebindable other) + { + this = other.trustedPayload; + } + + // must not escape when S is immutable + private + ref trustedPayload() @trusted + { + return *cast(S*)mutPayload.ptr; + } + + static if (!is(S == immutable)) + ref S Rebindable_getRef() @property + { + // payload exposed as const ref when S is const + return trustedPayload; + } + + static if (is(S == immutable)) + S Rebindable_get() @property + { + // we return a copy for immutable S + return trustedPayload; + } + + static if (is(S == immutable)) + alias Rebindable_get this; + else + alias Rebindable_getRef this; + + private + auto movePayload() @trusted + { + import std.algorithm : move; + return cast(S)move(*cast(Unqual!S*)mutPayload.ptr); + } + + ~this() + { + // call destructor with proper constness + movePayload; + } + } +} + +/// +@safe unittest +{ + static struct S + { + int* ptr; + } + S s = S(new int); + + const cs = s; + // Can't assign s.ptr to cs.ptr + static assert(!__traits(compiles, {s = cs;})); + + Rebindable!(const S) rs = s; + assert(rs.ptr is s.ptr); + // rs.ptr is const + static assert(!__traits(compiles, {rs.ptr = null;})); + + // Can't assign s.ptr to rs.ptr + static assert(!__traits(compiles, {s = rs;})); + + const S cs2 = rs; + // Rebind rs + rs = cs2; + rs = S(); + assert(rs.ptr is null); +} + +// Test Rebindable!immutable +@safe unittest +{ + static struct S + { + int* ptr; + } + S s = S(new int); + + Rebindable!(immutable S) ri = S(new int); + assert(ri.ptr !is null); + static assert(!__traits(compiles, {ri.ptr = null;})); + + // ri is not compatible with mutable S + static assert(!__traits(compiles, {s = ri;})); + static assert(!__traits(compiles, {ri = s;})); + + auto ri2 = ri; + assert(ri2.ptr == ri.ptr); + + const S cs3 = ri; + static assert(!__traits(compiles, {ri = cs3;})); + + immutable S si = ri; + // Rebind ri + ri = si; + ri = S(); + assert(ri.ptr is null); + + // Test RB!immutable -> RB!const + Rebindable!(const S) rc = ri; + assert(rc.ptr is null); + ri = S(new int); + rc = ri; + assert(rc.ptr !is null); + + // test rebindable, opAssign + rc.destroy; + assert(rc.ptr is null); + rc = rebindable(cs3); + rc = rebindable(si); + assert(rc.ptr !is null); + + ri.destroy; + assert(ri.ptr is null); + ri = rebindable(si); + assert(ri.ptr !is null); +} + +// Test Rebindable!mutable +@safe unittest +{ + static struct S + { + int* ptr; + } + S s; + + Rebindable!S rs = s; + static assert(is(typeof(rs) == S)); + rs = rebindable(S()); +} + +// Test disabled default ctor +unittest +{ + static struct ND + { + int i; + @disable this(); + this(int i) inout {this.i = i;} + } + static assert(!__traits(compiles, Rebindable!ND())); + + Rebindable!(const ND) rb = const ND(1); + assert(rb.i == 1); + rb = immutable ND(2); + assert(rb.i == 2); + rb = rebindable(const ND(3)); + assert(rb.i == 3); + static assert(!__traits(compiles, rb.i++)); +} + + /** Similar to $(D Rebindable!(T)) but strips all qualifiers from the reference as opposed to just constness / immutability. Primary intended use case is with