www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - Strange copying of a constant array of structures

reply Vindex <tech.vindex gmail.com> writes:
Last night I encountered some strange behavior of the dup 
function.

```
import std.stdio;

struct S {
     int x;
     int y;
     int[] arr;
     this(ref return scope const S rhs) {
         writeln("copy ctor");
         this.x = rhs.x;
         this.y = rhs.y;
         this.arr = rhs.arr.dup;
     }
}

void main() {
     const S[] array = [S(0, 0), S(1, 2)];
     S[] copy = array.dup;  // error
}
```

We have an issue:
```
Error: none of the overloads of template `object.dup` are 
callable using argument types `!()(const(S[]))`
```

But(!) if we remove the dynamic array field from the structure, 
everything works.


I decided to get around the problem by writing my own function to 
copy arrays:

```
T[] copyArray(T)(const T[] arr) {
     T[] copy = new T[arr.length];
     for (size_t i = 0; i < arr.length; i++) {
         copy[i] = arr[i];  // error
     }
     return copy;
}

void main() {
     const S[] array = [S(0, 0), S(1, 2)];
     S[] copy = copyArray(array);
}
```

Nice, simple function, but it doesn't compile on the assignment 
line:
```
Error: cannot implicitly convert expression `arr[i]` of type 
`const(S)` to `S`
```
(The feature is the same: if we remove the dynamic array field 
from the structure, everything works.)

An additional variable solution worked:

```
T[] copyArray(T)(const T[] arr) {
     T[] copy = new T[arr.length];
     for (size_t i = 0; i < arr.length; i++) {
         T elem = arr[i];  // copy ctor is called
         copy[i] = elem;  // copy ctor isn't called!
     }
     return copy;
}
```

I feel that I do not understand something, please explain what is 
the problem of constant structures with reference fields?

And why is the copy constructor only called once in the last 
example? Optimization?
Jun 14
next sibling parent reply user1234 <user1234 12.de> writes:
On Friday, 14 June 2024 at 08:03:47 UTC, Vindex wrote:
 Last night I encountered some strange behavior of the dup [...]
 I feel that I do not understand something, please explain what 
 is the problem of constant structures with reference fields?
`const` is transitive, not only `S` instances are but also their members.
 And why is the copy constructor only called once in the last 
 example? Optimization?
Yes kind of optim. The compiler is allowed to use "move-semantics". Looks like it's what happens here (at first glance).
Jun 14
parent Vindex <tech.vindex gmail.com> writes:
 And why is the copy constructor only called once in the last 
 example? Optimization?
Yes kind of optim. The compiler is allowed to use "move-semantics". Looks like it's what happens here (at first glance).
I'm guessing it's the implicit `opAssign` method after all. The objects in the copy have already been created and initialized as T.init, so the copy constructor will not be called in any way.
Jun 14
prev sibling next sibling parent reply vit <vit vit.vit> writes:
On Friday, 14 June 2024 at 08:03:47 UTC, Vindex wrote:
 Last night I encountered some strange behavior of the dup 
 function.

 ```
 import std.stdio;

 struct S {
     int x;
     int y;
     int[] arr;
     this(ref return scope const S rhs) {
         writeln("copy ctor");
         this.x = rhs.x;
         this.y = rhs.y;
         this.arr = rhs.arr.dup;
     }
 }

 void main() {
     const S[] array = [S(0, 0), S(1, 2)];
     S[] copy = array.dup;  // error
 }
 ```

 We have an issue:
 ```
 Error: none of the overloads of template `object.dup` are 
 callable using argument types `!()(const(S[]))`
 ```

 But(!) if we remove the dynamic array field from the structure, 
 everything works.
This is declaration of dup: ```` property T[] dup(T)(const(T)[] a) if (is(const(T) : T)) { import core.internal.array.duplication : _dup; return _dup!(const(T), T)(a); } ```` constraint is(const(T) : T) ignore copy ctors. If S has property arr then const(S) is not implicitly convertable to mutable S.
Jun 14
parent Vindex <tech.vindex gmail.com> writes:
Thanks!

So the array copy function implementation I showed is the best 
option?
Jun 14
prev sibling parent reply Steven Schveighoffer <schveiguy gmail.com> writes:
On Friday, 14 June 2024 at 08:03:47 UTC, Vindex wrote:
 Last night I encountered some strange behavior of the dup 
 function.

 ```
 import std.stdio;

 struct S {
     int x;
     int y;
     int[] arr;
     this(ref return scope const S rhs) {
         writeln("copy ctor");
         this.x = rhs.x;
         this.y = rhs.y;
         this.arr = rhs.arr.dup;
     }
 }

 void main() {
     const S[] array = [S(0, 0), S(1, 2)];
     S[] copy = array.dup;  // error
 }
 ```

 We have an issue:
 ```
 Error: none of the overloads of template `object.dup` are 
 callable using argument types `!()(const(S[]))`
 ```

 But(!) if we remove the dynamic array field from the structure, 
 everything works.
I think the fact that `dup` is not using the copy ctor is a bug. This was recently reported: https://issues.dlang.org/show_bug.cgi?id=24432
 I decided to get around the problem by writing my own function 
 to copy arrays:

 ```
 T[] copyArray(T)(const T[] arr) {
     T[] copy = new T[arr.length];
     for (size_t i = 0; i < arr.length; i++) {
         copy[i] = arr[i];  // error
     }
     return copy;
 }

 void main() {
     const S[] array = [S(0, 0), S(1, 2)];
     S[] copy = copyArray(array);
 }
 ```

 Nice, simple function, but it doesn't compile on the assignment 
 line:
 ```
 Error: cannot implicitly convert expression `arr[i]` of type 
 `const(S)` to `S`
 ```
 (The feature is the same: if we remove the dynamic array field 
 from the structure, everything works.)
Yes, you are *assigning*, not *constructing*. You could potentially make it work using `core.lifetime.emplace`, which treats it like a construction. In order to make this work, you need an appropriate `opAssign`.
 An additional variable solution worked:

 ```
 T[] copyArray(T)(const T[] arr) {
     T[] copy = new T[arr.length];
     for (size_t i = 0; i < arr.length; i++) {
         T elem = arr[i];  // copy ctor is called
         copy[i] = elem;  // copy ctor isn't called!
     }
     return copy;
 }
 ```

 I feel that I do not understand something, please explain what 
 is the problem of constant structures with reference fields?
So this is *constructing* `elem` as a non-const T. This calls the copy constructor. The assignment just does a bit-copy of the first value into the second, but since both are not const, it works fine. Construction happens on *initialization*, that is, declaring a variable and specifying an initial value. Assignment happens when assigning to an *existing* variable. What is the difference? In construction, the compiler knows that the values in the type have never been assigned a value before. So it allows certain things (e.g. assigning to an immutable value). -Steve
Jun 15
parent Vindex <tech.vindex gmail.com> writes:
On Sunday, 16 June 2024 at 02:50:20 UTC, Steven Schveighoffer 
wrote:
 On Friday, 14 June 2024 at 08:03:47 UTC, Vindex wrote:
 [...]
I think the fact that `dup` is not using the copy ctor is a bug. This was recently reported: https://issues.dlang.org/show_bug.cgi?id=24432 [...]
Thank you! Especially for information about bug report.
Jun 16