Thread overview
Strange copying of a constant array of structures
Jun 14
Vindex
Jun 14
user1234
Jun 14
Vindex
Jun 14
vit
Jun 14
Vindex
Jun 16
Vindex
June 14

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?

June 14

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).

June 14

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.

June 14

Thanks!

So the array copy function implementation I showed is the best option?

June 14
> >

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.

June 16

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

June 16

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.