| module core.internal.array.arrayassign; | |
| // Force `enforceRawArraysConformable` to remain `pure` `@nogc` | |
| private void enforceRawArraysConformable(const char[] action, const size_t elementSize, | |
| const void[] a1, const void[] a2, const bool allowOverlap) @trusted @nogc pure nothrow | |
| { | |
| import core.internal.util.array : enforceRawArraysConformable; | |
| alias Type = void function(const char[] action, const size_t elementSize, | |
| const void[] a1, const void[] a2, in bool allowOverlap = false) @nogc pure nothrow; | |
| (cast(Type)&enforceRawArraysConformable)(action, elementSize, a1, a2, allowOverlap); | |
| } | |
| private template CopyElem(string CopyAction) | |
| { | |
| const char[] CopyElem = "{\n" ~ q{ | |
| memcpy(&tmp, cast(void*) &dst, elemSize); | |
| } ~ CopyAction ~ q{ | |
| auto elem = cast(Unqual!T*) &tmp; | |
| destroy(*elem); | |
| } ~ "}\n"; | |
| } | |
| private template CopyArray(bool CanOverlap, string CopyAction) | |
| { | |
| const char[] CopyArray = CanOverlap ? q{ | |
| if (vFrom.ptr < vTo.ptr && vTo.ptr < vFrom.ptr + elemSize * vFrom.length) | |
| foreach_reverse (i, ref dst; to) | |
| } ~ CopyElem!(CopyAction) ~ q{ | |
| else | |
| foreach (i, ref dst; to) | |
| } ~ CopyElem!(CopyAction) | |
| : q{ | |
| foreach (i, ref dst; to) | |
| } ~ CopyElem!(CopyAction); | |
| } | |
| private template ArrayAssign(string CopyLogic, string AllowOverLap) | |
| { | |
| const char[] ArrayAssign = q{ | |
| import core.internal.traits : hasElaborateCopyConstructor, Unqual; | |
| import core.lifetime : copyEmplace; | |
| import core.stdc.string : memcpy; | |
| void[] vFrom = (cast(void*) from.ptr)[0 .. from.length]; | |
| void[] vTo = (cast(void*) to.ptr)[0 .. to.length]; | |
| enum elemSize = T.sizeof; | |
| enforceRawArraysConformable("copy", elemSize, vFrom, vTo, } ~ AllowOverLap ~ q{); | |
| void[elemSize] tmp = void; | |
| } ~ CopyLogic ~ q{ | |
| return to; | |
| }; | |
| } | |
| /** | |
| * Does array assignment (not construction) from another array of the same | |
| * element type. Handles overlapping copies. Assumes the right hand side is an | |
| * lvalue, | |
| * | |
| * Used for static array assignment with non-POD element types: | |
| * --- | |
| * struct S | |
| * { | |
| * ~this() {} // destructor, so not Plain Old Data | |
| * } | |
| * | |
| * void main() | |
| * { | |
| * S[3] arr; | |
| * S[3] lvalue; | |
| * | |
| * arr = lvalue; | |
| * // Generates: | |
| * // _d_arrayassign_l(arr[], lvalue[]), arr; | |
| * } | |
| * --- | |
| * | |
| * Params: | |
| * to = destination array | |
| * from = source array | |
| * Returns: | |
| * `to` | |
| */ | |
| Tarr _d_arrayassign_l(Tarr : T[], T)(return scope Tarr to, scope Tarr from) @trusted | |
| { | |
| mixin(ArrayAssign!(q{ | |
| static if (hasElaborateCopyConstructor!T) | |
| } ~ CopyArray!(true, "copyEmplace(from[i], dst);") ~ q{ | |
| else | |
| } ~ CopyArray!(true, "memcpy(cast(void*) &dst, cast(void*) &from[i], elemSize);"), | |
| "true")); | |
| } | |
| @safe unittest | |
| { | |
| int counter; | |
| struct S | |
| { | |
| int val; | |
| this(int val) { this.val = val; } | |
| this(const scope ref S rhs) | |
| { | |
| val = rhs.val; | |
| counter++; | |
| } | |
| } | |
| S[4] arr1; | |
| S[4] arr2 = [S(0), S(1), S(2), S(3)]; | |
| _d_arrayassign_l(arr1[], arr2[]); | |
| assert(counter == 4); | |
| assert(arr1 == arr2); | |
| } | |
| // copy constructor | |
| @safe unittest | |
| { | |
| int counter; | |
| struct S | |
| { | |
| int val; | |
| this(int val) { this.val = val; } | |
| this(const scope ref S rhs) | |
| { | |
| val = rhs.val; | |
| counter++; | |
| } | |
| } | |
| S[4] arr1; | |
| S[4] arr2 = [S(0), S(1), S(2), S(3)]; | |
| _d_arrayassign_l(arr1[], arr2[]); | |
| assert(counter == 4); | |
| assert(arr1 == arr2); | |
| } | |
| @safe nothrow unittest | |
| { | |
| // Test that throwing works | |
| int counter; | |
| bool didThrow; | |
| struct Throw | |
| { | |
| int val; | |
| this(this) | |
| { | |
| counter++; | |
| if (counter == 2) | |
| throw new Exception(""); | |
| } | |
| } | |
| try | |
| { | |
| Throw[4] a; | |
| Throw[4] b = [Throw(1), Throw(2), Throw(3), Throw(4)]; | |
| _d_arrayassign_l(a[], b[]); | |
| } | |
| catch (Exception) | |
| { | |
| didThrow = true; | |
| } | |
| assert(didThrow); | |
| assert(counter == 2); | |
| // Test that `nothrow` works | |
| didThrow = false; | |
| counter = 0; | |
| struct NoThrow | |
| { | |
| int val; | |
| this(this) | |
| { | |
| counter++; | |
| } | |
| } | |
| try | |
| { | |
| NoThrow[4] a; | |
| NoThrow[4] b = [NoThrow(1), NoThrow(2), NoThrow(3), NoThrow(4)]; | |
| _d_arrayassign_l(a[], b[]); | |
| } | |
| catch (Exception) | |
| { | |
| didThrow = false; | |
| } | |
| assert(!didThrow); | |
| assert(counter == 4); | |
| } | |
| /** | |
| * Does array assignment (not construction) from another array of the same | |
| * element type. Does not support overlapping copies. Assumes the right hand | |
| * side is an rvalue, | |
| * | |
| * Used for static array assignment with non-POD element types: | |
| * --- | |
| * struct S | |
| * { | |
| * ~this() {} // destructor, so not Plain Old Data | |
| * } | |
| * | |
| * void main() | |
| * { | |
| * S[3] arr; | |
| * S[3] getRvalue() {return lvalue;} | |
| * | |
| * arr = getRvalue(); | |
| * // Generates: | |
| * // (__appendtmp = getRvalue), _d_arrayassign_l(arr[], __appendtmp), arr; | |
| * } | |
| * --- | |
| * | |
| * Params: | |
| * to = destination array | |
| * from = source array | |
| * Returns: | |
| * `to` | |
| */ | |
| Tarr _d_arrayassign_r(Tarr : T[], T)(return scope Tarr to, scope Tarr from) @trusted | |
| { | |
| mixin(ArrayAssign!( | |
| CopyArray!(false, "memcpy(cast(void*) &dst, cast(void*) &from[i], elemSize);"), | |
| "false")); | |
| } | |
| @safe unittest | |
| { | |
| int counter; | |
| struct S | |
| { | |
| int val; | |
| this(int val) { this.val = val; } | |
| this(const scope ref S rhs) | |
| { | |
| val = rhs.val; | |
| counter++; | |
| } | |
| } | |
| S[4] arr1; | |
| S[4] arr2 = [S(0), S(1), S(2), S(3)]; | |
| _d_arrayassign_r(arr1[], arr2[]); | |
| assert(counter == 0); | |
| assert(arr1 == arr2); | |
| } | |
| // copy constructor | |
| @safe unittest | |
| { | |
| int counter; | |
| struct S | |
| { | |
| int val; | |
| this(int val) { this.val = val; } | |
| this(const scope ref S rhs) | |
| { | |
| val = rhs.val; | |
| counter++; | |
| } | |
| } | |
| S[4] arr1; | |
| S[4] arr2 = [S(0), S(1), S(2), S(3)]; | |
| _d_arrayassign_r(arr1[], arr2[]); | |
| assert(counter == 0); | |
| assert(arr1 == arr2); | |
| } | |
| @safe nothrow unittest | |
| { | |
| // Test that `nothrow` works | |
| bool didThrow = false; | |
| int counter = 0; | |
| struct NoThrow | |
| { | |
| int val; | |
| this(this) | |
| { | |
| counter++; | |
| } | |
| } | |
| try | |
| { | |
| NoThrow[4] a; | |
| NoThrow[4] b = [NoThrow(1), NoThrow(2), NoThrow(3), NoThrow(4)]; | |
| _d_arrayassign_r(a[], b[]); | |
| } | |
| catch (Exception) | |
| { | |
| didThrow = false; | |
| } | |
| assert(!didThrow); | |
| assert(counter == 0); | |
| } | |
| /** | |
| * Sets all elements of an array to a single value. Takes into account postblits, | |
| * copy constructors and destructors. For Plain Old Data elements,`rt/memset.d` | |
| * is used. | |
| * | |
| * --- | |
| * struct S | |
| * { | |
| * ~this() {} // destructor, so not Plain Old Data | |
| * } | |
| * | |
| * void main() | |
| * { | |
| * S[3] arr; | |
| * S value; | |
| * | |
| * arr = value; | |
| * // Generates: | |
| * // _d_arraysetassign(arr[], value), arr; | |
| * } | |
| * --- | |
| * | |
| * Params: | |
| * to = destination array | |
| * value = the element to set | |
| * Returns: | |
| * `to` | |
| */ | |
| Tarr _d_arraysetassign(Tarr : T[], T)(return scope Tarr to, scope ref T value) @trusted | |
| { | |
| import core.internal.traits : Unqual; | |
| import core.lifetime : copyEmplace; | |
| import core.stdc.string : memcpy; | |
| enum elemSize = T.sizeof; | |
| void[elemSize] tmp = void; | |
| foreach (ref dst; to) | |
| { | |
| memcpy(&tmp, cast(void*) &dst, elemSize); | |
| // Use `memcpy` if `T` has a `@disable`d postblit. | |
| static if (__traits(isCopyable, T)) | |
| copyEmplace(value, dst); | |
| else | |
| memcpy(cast(void*) &dst, cast(void*) &value, elemSize); | |
| auto elem = cast(Unqual!T*) &tmp; | |
| destroy(*elem); | |
| } | |
| return to; | |
| } | |
| // postblit and destructor | |
| @safe unittest | |
| { | |
| string ops; | |
| struct S | |
| { | |
| int val; | |
| this(this) { ops ~= "="; } | |
| ~this() { ops ~= "~"; } | |
| } | |
| S[4] arr; | |
| S s = S(1234); | |
| _d_arraysetassign(arr[], s); | |
| assert(ops == "=~=~=~=~"); | |
| assert(arr == [S(1234), S(1234), S(1234), S(1234)]); | |
| } | |
| // copy constructor | |
| @safe unittest | |
| { | |
| string ops; | |
| struct S | |
| { | |
| int val; | |
| this(const scope ref S rhs) | |
| { | |
| val = rhs.val; | |
| ops ~= "="; | |
| } | |
| ~this() { ops ~= "~"; } | |
| } | |
| S[4] arr; | |
| S s = S(1234); | |
| _d_arraysetassign(arr[], s); | |
| assert(ops == "=~=~=~=~"); | |
| assert(arr == [S(1234), S(1234), S(1234), S(1234)]); | |
| } | |
| // disabled copy constructor | |
| @safe unittest | |
| { | |
| static struct S | |
| { | |
| int val; | |
| @disable this(ref S); | |
| } | |
| S[1] arr; | |
| S s = S(1234); | |
| _d_arraysetassign(arr[], s); | |
| assert(arr[0].val == 1234); | |
| } | |
| // throwing and `nothrow` | |
| @safe nothrow unittest | |
| { | |
| // Test that throwing works | |
| bool didThrow; | |
| int counter; | |
| struct Throw | |
| { | |
| int val; | |
| this(this) | |
| { | |
| counter++; | |
| if (counter == 2) | |
| throw new Exception("Oh no."); | |
| } | |
| } | |
| try | |
| { | |
| Throw[4] a; | |
| Throw b = Throw(1); | |
| _d_arraysetassign(a[], b); | |
| } | |
| catch (Exception) | |
| { | |
| didThrow = true; | |
| } | |
| assert(didThrow); | |
| assert(counter == 2); | |
| // Test that `nothrow` works | |
| didThrow = false; | |
| counter = 0; | |
| struct NoThrow | |
| { | |
| int val; | |
| this(this) { counter++; } | |
| } | |
| try | |
| { | |
| NoThrow[4] a; | |
| NoThrow b = NoThrow(1); | |
| _d_arraysetassign(a[], b); | |
| foreach (ref e; a) | |
| assert(e == NoThrow(1)); | |
| } | |
| catch (Exception) | |
| { | |
| didThrow = true; | |
| } | |
| assert(!didThrow); | |
| // The array `a` is destroyed when the `try` block ends. | |
| assert(counter == 4); | |
| } |