www.digitalmars.com         C & C++   DMDScript  

digitalmars.D.learn - Copying array with const correctness

reply Vindex9 <tech.vindex gmail.com> writes:
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.

```d
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[][]`.
Oct 07
next sibling parent reply Vindex9 <tech.vindex gmail.com> writes:
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:

```d
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.
Oct 07
parent reply Vindex9 <tech.vindex gmail.com> writes:
On Tuesday, 7 October 2025 at 19:07:28 UTC, Vindex9 wrote:
 alternative to Dub
* alternative to dup property
Oct 07
parent Vindex9 <tech.vindex gmail.com> writes:
So far, the simplest solution is to create a separate function 
for a two-dimensional array:

```d
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.
Oct 07
prev sibling parent reply Steven Schveighoffer <schveiguy gmail.com> writes:
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: ```d 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: ```d 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
Oct 07
parent reply Vindex9 <tech.vindex gmail.com> writes:
On Wednesday, 8 October 2025 at 02:58:15 UTC, Steven 
Schveighoffer wrote:
 My attempt:

 ```d
 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. ```d 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.
Oct 08
next sibling parent reply Vindex9 <tech.vindex gmail.com> writes:
Here's a slightly better solution. The lines will be copied.

```d
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;
}
```
Oct 08
parent Vindex9 <tech.vindex gmail.com> writes:
More accurately:

```d
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;
}
```
Oct 08
prev sibling parent Steven Schveighoffer <schveiguy gmail.com> writes:
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:

 ```d
 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
Oct 08