Thread overview
Copying array with const correctness
Oct 07
Vindex9
Oct 07
Vindex9
Oct 07
Vindex9
Oct 07
Vindex9
Oct 08
Vindex9
Oct 08
Vindex9
Oct 08
Vindex9
October 07

I'm trying to write a function for copying an array. But something strange is happening with the const correctness. I cannot remove the constitution for the case of a two-dimensional array of lines.

import std.stdio;
import std.traits : Unconst;

auto copyArray(T)(const T[] arr) if (!is(T == class)) {
    alias E = Unconst!T;
    E[] copy = new E[arr.length];

    writeln("orig type: ", typeid(T));  // orig type: const(immutable(char)[])[]
    writeln("copy type: ", typeid(E));  // copy type: const(immutable(char)[])[]
    writeln("copy type: ", typeid(Unconst!E));  // copy type: const(immutable(char)[])[]

    static if (is(T : const(E)[])) {  // if inner array (doesn't work)
        writeln("No");
        for (size_t i = 0; i < arr.length; i++) {
            copy[i] = copyArray(arr[i]);
        }
    } else {
        for (size_t i = 0; i < arr.length; i++) {
            E elem = arr[i];
            copy[i] = elem;
        }
    }
    return copy;
}


void main() {
	const string[][] arr = [["ABC", "DEF"], ["GHI", "JKL"]];
    writeln(typeid(arr));  // const(const(const(immutable(char)[])[])[])
    auto res = copyArray(arr);
    writeln(typeid(res));  // const(immutable(char)[])[][]
}

What am I doing wrong?
I want a safe copy: const string[][] -> string[][].

October 07

I’d like to clarify: the original goal was to create an alternative to Dub in order to make copies of arrays of objects that contain copying constructors.

It's all good like this:

unittest
{

    T[] copyArray(T)(const T[] arr) if (!is(T == class)) {
        T[] copy = new T[arr.length];
        for (size_t i = 0; i < arr.length; i++) {
            // it doesn't work with postblit, only with modern copy ctor
            T elem = arr[i];
            copy[i] = elem;
        }
        return copy;
    }

    struct S {
        int x, y;
        bool[] a;
        this(ref return scope const S rhs) {
            this.x = rhs.x;
            this.y = rhs.y;
            this.a = rhs.a.dup;
        }
    }

    const S[] arr = [
        S(1, 2, [true, false]),
        S(3, 4, [false, true])
    ];
    // S[] copy = arr.dup;  // It can't be compiled!
    S[] copy = copyArray(arr);   // from const array to non-const one

    assert(copy.length == arr.length);
    for (size_t i = 0; i < arr.length; i++) {
        assert(arr[i].x == copy[i].x);
        assert(arr[i].y == copy[i].y);
        assert(arr[i].a == copy[i].a);
        assert(arr[i].a.ptr != copy[i].a.ptr);
    }

    const S[] emptyArr = [];
    assert(emptyArr.ptr == null);
    S[] emptyCopy = copyArray(emptyArr);
    assert(emptyCopy.ptr == null);
}

The problems started when I decided to increase the versatility.

October 07

On Tuesday, 7 October 2025 at 19:07:28 UTC, Vindex9 wrote:

>

alternative to Dub

  • alternative to dup property
October 07

So far, the simplest solution is to create a separate function for a two-dimensional array:

auto copy2DArray(T)(const T[][] arr) if (!is(T == class)) {
    T[][] copy;
    foreach(row; arr) {
        T[] tmp;
        foreach(field; row) {
            tmp ~= field;
        }
        copy ~= tmp;
    }
    return copy;
}

However, the metamorphoses of the types from my first example are very mysterious.

October 08

On Tuesday, 7 October 2025 at 18:43:18 UTC, Vindex9 wrote:

>

What am I doing wrong?
I want a safe copy: const string[][] -> string[][].

I think the approach should be, make a copy, then cast the copy, recurse if it's a nested array.

What you are likely running into is that D automatically strips the top-level const from a templated array.

That is:

void foo(T)(T[] arr) {
    pragma(msg, "in foo: ", typeof(arr)); // const(int)[]
}

void main()
{
   const int[] arr;
   pragma(msg, "in main: ", typeof(arr)); // const(int[])
   foo(arr);
}

On top of that, you are adding const to the incoming type, which can be problematic.

What I'd recommend is inout instead. This will unwrap to the original type but still keep you from modifying the original (like const).

My attempt:

import std.traits;

inout(T)[] copyArray(T)(inout(T)[] arr) {
    alias M = Unqual!T;
    M[] result;
    result.length = arr.length;
    foreach(i, ref v; result) {
        static if(is(T == U[], U)) // it's an array of arrays
            v = cast(M)copyArray(arr[i]);
        else
            v = cast(M)arr[i];
    }

    return cast(typeof(return))result;
}

void main()
{
    import std.stdio;
  	const string[][] arr = [["ABC", "DEF"], ["GHI", "JKL"]];
    writeln(typeid(arr));  // const(const(const(immutable(char)[])[])[])
    const res = copyArray(arr); // note the const here, but still typesafe without!
    writeln(typeid(res));  // const(const(const(immutable(char)[])[])[])
}

-Steve

October 08

On Wednesday, 8 October 2025 at 02:58:15 UTC, Steven Schveighoffer wrote:

>

My attempt:

import std.traits;

inout(T)[] copyArray(T)(inout(T)[] arr) {
    alias M = Unqual!T;

Unfortunately, Unqual in your code doesn't do anything - the type M remains T. Apparently, some strange things are happening inside the template when it comes to constness.

Sometimes you need to return an array from a const method. You don't want modifications to the returned array to affect the state of the struct, but at the same time, you want to freely manipulate the consents of that array. That's why removing constness is important to me. However, it seems that making a recursive copy isn't necessary: when it comes to strings, we don't need to turn a string into a char[]. As s way out, we can simply avoid removing constness from immutable data. It seems I have managed to reach the goal without resorting to explicit conversions.

import std.stdio;

T[] copyArray(T)(inout(T)[] arr) {
    T[] result;
    result.length = arr.length;
    static if(is(T == U[], U) && !is(T == immutable(Y)[], Y)) {
        foreach(i, ref v; result) {
            v = copyArray(arr[i]);
        }
    } else {
        foreach(i, ref v; result) {
            T middle = arr[i];
            v = middle;
        }
    }
    return result;
}


struct S {
    int x;
    bool[] a;
    this(ref return scope const S rhs) {
        this.x = rhs.x;
        this.a = rhs.a.dup;
    }
}


void main() {
    const string[] arr1d = ["ABC", "DEF"];  // const(const(immutable(char)[])[])
    writeln(typeid(arr1d));
    auto res1 = copyArray(arr1d);
    writeln(typeid(res1));  // immutable(char)[][]

    const string[][] arr2d = [["ABC", "DEF"], ["GHI", "JKL"]];
    writeln(typeid(arr2d));  // const(const(const(immutable(char)[])[])[])
    auto res2 = copyArray(arr2d);
    writeln(typeid(res2));  // immutable(char)[][][]

    const S[] structArr = [S(8, [true, false]), S(9, [false, true])];
    writeln(typeid(structArr));  // const(const(onlineapp.S)[])
    auto structArrCopy = copyArray(structArr);
    writeln(structArrCopy);
    writeln(typeid(structArrCopy));  // onlineapp.S[]

    const int[][] intArr = [[1, 2], [3, 4]];
    writeln(typeid(intArr));  // const(const(const(int)[])[])
    auto intArrCopy = copyArray(intArr);
    writeln(intArrCopy);
    writeln(typeid(intArrCopy));  // int[][]
}

Thank you, Steve.

October 08

Here's a slightly better solution. The lines will be copied.

T[] copyArray(T)(inout(T)[] arr) {
    T[] copy = new T[arr.length];
    copy.length = arr.length;
    static if (is(T == U[], U) && !is(T == immutable(Y)[], Y)) {
        foreach(i, ref v; copy) {
            v = copyArray(arr[i]);
        }
    } else {
        for (size_t i = 0; i < arr.length; i++) {
            static if (is(T == immutable(Y)[], Y)) {
                copy[i] = arr[i].idup;
            } else {
                // it doesn't work with postblit, only with modern copy ctor
                T elem = arr[i];
                copy[i] = elem;
            }
        }
    }
    return copy;
}
October 08

More accurately:

T[] copyArray(T)(inout(T)[] arr) {
    T[] copy = new T[arr.length];
    static if (is(T == U[], U) && !is(T == immutable(Y)[], Y)) {
        foreach(i, ref v; copy) {
            v = copyArray(arr[i]);
        }
    } else static if (is(T == immutable(W)[], W)) {
        for (size_t i = 0; i < arr.length; i++) {
            copy[i] = arr[i].idup;
        }
    } else {
        for (size_t i = 0; i < arr.length; i++) {
            // it doesn't work with postblit, only with modern copy ctor
            T elem = arr[i];
            copy[i] = elem;
        }
    }
    return copy;
}
October 08

On Wednesday, 8 October 2025 at 07:46:32 UTC, Vindex9 wrote:

>

On Wednesday, 8 October 2025 at 02:58:15 UTC, Steven Schveighoffer wrote:

>

My attempt:

import std.traits;

inout(T)[] copyArray(T)(inout(T)[] arr) {
    alias M = Unqual!T;

Unfortunately, Unqual in your code doesn't do anything - the type M remains T. Apparently, some strange things are happening inside the template when it comes to constness.

You are right, I didn't think about the fact that T is going to already be unmodified (since inout takes over the modifier).

So really, this isn't needed, you can just use T, and rely on the explicit conversion at the end.

>

Thank you, Steve.

I'm glad you were able to work it out.

-Steve