diff --git a/std/typecons.d b/std/typecons.d index 25cf9e00841..ab80698589c 100644 --- a/std/typecons.d +++ b/std/typecons.d @@ -2332,7 +2332,7 @@ break the soundness of D's type system and does not incur any of the risks usually associated with `cast`. Params: - T = An object, interface, array slice type, or associative array type. + T = Any type. */ template Rebindable(T) if (is(T == class) || is(T == interface) || isDynamicArray!T || isAssociativeArray!T) @@ -2473,23 +2473,344 @@ if (is(T == class) || is(T == interface) || isDynamicArray!T || isAssociativeArr })); } +/** + * Models safe reassignment of otherwise constant types. + * + * `Rebindable!S` always makes a copy. + */ +struct Rebindable(S) +if (!is(S == class) && !is(S == interface) && !isDynamicArray!S && !isAssociativeArray!S) +{ +private: + ManagedLifetime!S payload; + +public: + static if (!__traits(compiles, { S s; })) + { + @disable this(); + } + + this(S s) @trusted + { + payload.set(s); + } + + void opAssign(this This)(S s) @trusted + { + payload.clear; + payload.set(s); + } + + void opAssign(this This)(Rebindable other) @trusted + { + payload.clear; + payload.set(other.payload.get); + } + + S get(this This)() @property @trusted + { + return payload.get; + } + + alias get this; + + ~this() @trusted + { + payload.clear; + } +} + +/// +@system 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); +} + +/// Using Rebindable in a generic algorithm: +@safe unittest +{ + import std.range.primitives : front, popFront; + + // simple version of std.algorithm.searching.maxElement + typeof(R.init.front) maxElement(R)(R r) + { + auto max = rebindable(r.front); + r.popFront; + foreach (e; r) + if (e > max) + max = e; // Rebindable allows const-correct reassignment + return max; + } + struct S + { + char[] arr; + alias arr this; // for comparison + } + // can't convert to mutable + const S cs; + static assert(!__traits(compiles, { S s = cs; })); + + alias CS = const S; + CS[] arr = [CS("harp"), CS("apple"), CS("pot")]; + CS ms = maxElement(arr); + assert(ms.arr == "pot"); +} + +// 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 disabled default ctor +@safe 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++)); +} + +// Test copying +@safe unittest +{ + int del; + int post; + struct S + { + int* ptr; + int level; + this(this) { + post++; + level++; + } + ~this() { + del++; + } + } + + // test ref count + { + Rebindable!S rc = S(new int); + } + assert(post == del - 1); +} + +/** + * Permits explicit control of the lifetime of a contained `T`. + * + * This works regardless of the constness of `T`. + * + * The container starts out in an empty state. It may only be assigned to when empty, + * and only once; it must be cleared before the next assignment. + * If the container contains a value when its lifetime expires, the destructor of the + * contained value will not be run. + * + * It is up to you to manage the lifetime of the contained value explicitly! + */ +struct ManagedLifetime(T) +{ +private: + MutableImitation!T mutPayload; + +public: + /** + * Initializes the container with a value. + */ + this(T value) + { + set(value); + } + + /** + * Set the container to a value. + * The container must be empty. + */ + void set(this This)(T value) + { + // as `mutPayload` won't call the destructor, we deliberately leak a copy here: + static union DontCallDestructor + { + T value; + } + DontCallDestructor copy = DontCallDestructor(value); + mutPayload = *cast(MutableImitation!T*)© + } + + /** + * Destroy the value saved in the container. + * The container must be set to a value. + */ + void clear(this This)() + { + import std.typecons : No; + + // work around reinterpreting cast being impossible in CTFE + if (__ctfe) + { + return; + } + + static if (is(T == class) || is(T == interface)) + { + mutPayload = null; + } + else + { + // call possible struct destructors + .destroy!(No.initialize)(*cast(T*) &mutPayload); + } + } + + /** + * Set the container to a value if there is already a value in it. + * Convenience shortcut for `clear; set(value);` + */ + void replace(this This)(T value) + { + clear; + set(value); + } + + /** + * Return a copy of the stored value. + * The container must be set to a value. + */ + T get(this This)() @property + { + return *cast(T*) &mutPayload; + } +} + +/// +@system unittest +{ + int del; + int post; + struct S + { + int* ptr; + this(this) { + post++; + } + ~this() { + del++; + } + } + + { + ManagedLifetime!S container; + + assert(post == 0 && del == 0); + + container.set(S(new int)); + assert(post == 1 && del == 1); + + container.clear; + assert(post == 1 && del == 2); + } + assert(post == 1 && del == 2); +} + /** Convenience function for creating a `Rebindable` using automatic type inference. Params: - obj = A reference to an object, interface, associative array, or an array slice - to initialize the `Rebindable` with. + value = Value to be stored in the `Rebindable`. 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) +Rebindable!T rebindable(T)(T value) +if (!is(T == Rebindable!U, U)) { - typeof(return) ret; - ret = obj; - return ret; + static if (is(T == class) || is(T == interface) || isDynamicArray!T || isAssociativeArray!T) + { + Rebindable!T ret; + ret = value; + return ret; + } + else + { + return Rebindable!T(value); + } } /// @@ -2518,10 +2839,8 @@ if (is(T == class) || is(T == interface) || isDynamicArray!T || isAssociativeArr This function simply returns the `Rebindable` object passed in. It's useful in generic programming cases when a given object may be either a regular `class` or a `Rebindable`. - Params: obj = An instance of Rebindable!T. - Returns: `obj` without any modification. */ @@ -2626,10 +2945,6 @@ Rebindable!T rebindable(T)(Rebindable!T obj) static assert(is(Rebindable!(T[]) == T[])); } - // https://issues.dlang.org/show_bug.cgi?id=12046 - static assert(!__traits(compiles, Rebindable!(int[1]))); - static assert(!__traits(compiles, Rebindable!(const int[1]))); - // Pull request 3341 Rebindable!(immutable int[int]) pr3341 = [123:345]; assert(pr3341[123] == 345); @@ -2639,6 +2954,95 @@ Rebindable!T rebindable(T)(Rebindable!T obj) assert(rebindable(pr3341_aa)[321] == 543); } +/** + * A type "like" T, but mutable. + * Used internally in Rebindable!struct. + */ +private template MutableImitation(T) +{ + alias MutableImitation = MutableImitationImpl!T; + + // sanity checks + static assert(T.sizeof == MutableImitation.sizeof, "sizeof sanity check violated"); + static assert(T.alignof == MutableImitation.alignof, "alignof sanity check violated"); + static assert(hasIndirections!T == hasIndirections!MutableImitation, "indirection sanity check violated"); +} + +private template MutableImitationImpl(T) +{ + static if (is(T == struct)) + { + static if (anyPairwiseEqual!(staticMap!(offsetOf, T.tupleof))) + { + // Struct with anonymous unions detected! + // Danger, danger! + alias MutableImitationImpl = MutableImitationFallback!T; + } + else + { + align(T.alignof) + struct MutableImitationImpl + { + staticMap!(MutableImitation, typeof(T.init.tupleof)) fields; + } + } + } + else static if (is(T == union) || isAssociativeArray!T) + { + alias MutableImitationImpl = MutableImitationFallback!T; + } + else static if (is(T == function) || is(T == enum) || __traits(isArithmetic, T) + || is(T : U*, U) || is(T : K[], K) || is(T == delegate)) + { + // types where Unqual strips head constness + alias MutableImitationImpl = Unqual!T; + } + else static if (is(T == class) || is(T == interface)) + { + alias MutableImitationImpl = void*; + } + else static if (is(T == U[size], U, size_t size)) + { + alias MutableImitationImpl = MutableImitation!U[size]; + } + else + { + static assert(false, "Unsupported type " ~ T.stringof); + } +} + +private template MutableImitationFallback(T) +{ + align(T.alignof) + private struct MutableImitationFallback + { + static if (hasIndirections!T) + { + void[T.sizeof] data; + } + else + { + // union of non-pointer types? + ubyte[T.sizeof] data; + } + } +} + +private enum offsetOf(alias member) = member.offsetof; + +// True if any adjacent members of T are equal. +private template anyPairwiseEqual(T...) +{ + static if (T.length == 0 || T.length == 1) + { + enum anyPairwiseEqual = false; + } + else + { + enum anyPairwiseEqual = T[0] == T[1] || anyPairwiseEqual!(T[1 .. $]); + } +} + /** Similar to `Rebindable!(T)` but strips all qualifiers from the reference as opposed to just constness / immutability. Primary intended use case is with