Thread overview
Copying with immutable arrays
Oct 27, 2012
Tobias Pankrath
Oct 27, 2012
Ali Çehreli
Oct 28, 2012
Tobias Pankrath
Oct 29, 2012
Ali Çehreli
Oct 29, 2012
Tobias Pankrath
Oct 30, 2012
Don Clugston
October 27, 2012
So I have this immutable array with user defined structs but I can not
make a copy from it.

See:

struct SwA {
    string[] strings;
}

void main()
{
    immutable(SwA)[] arr1;
    SwA[] arr2 = arr1.dup;
}

Says:
Error: cannot implicitly convert element type immutable(SwA) to mutable in arr1.dup

Next try:

struct SwA {
    string[] strings;
}

void main()
{
    import std.algorithm;
    immutable(SwA)[] arr1;
    SwA[] arr2;
    copy(arr1, arr2);
}

Says:
test.d(11): Error: template std.algorithm.copy does not match any function template declaration
/home/tobias/projekte/d/dmd/src/../../phobos/std/algorithm.d(5859): Error: template std.algorithm.copy(Range1,Range2) if (isInputRange!(Range1) && isOutputRange!(Range2,ElementType!(Range1))) cannot deduce template function from argument types !()(immutable(SwA)[],SwA[])

(Which is a bug, I guess).

So can I do this without a cast?
October 27, 2012
On 10/27/2012 02:30 AM, Tobias Pankrath wrote:
> So I have this immutable array with user defined structs but I can not
> make a copy from it.
>
> See:
>
> struct SwA {
> string[] strings;
> }
>
> void main()
> {
> immutable(SwA)[] arr1;
> SwA[] arr2 = arr1.dup;
> }
>
> Says:
> Error: cannot implicitly convert element type immutable(SwA) to mutable
> in arr1.dup
>
> Next try:
>
> struct SwA {
> string[] strings;
> }
>
> void main()
> {
> import std.algorithm;
> immutable(SwA)[] arr1;
> SwA[] arr2;
> copy(arr1, arr2);
> }
>
> Says:
> test.d(11): Error: template std.algorithm.copy does not match any
> function template declaration
> /home/tobias/projekte/d/dmd/src/../../phobos/std/algorithm.d(5859):
> Error: template std.algorithm.copy(Range1,Range2) if
> (isInputRange!(Range1) && isOutputRange!(Range2,ElementType!(Range1)))
> cannot deduce template function from argument types
> !()(immutable(SwA)[],SwA[])
>
> (Which is a bug, I guess).
>
> So can I do this without a cast?

Casting may not do the right thing, as the immutable object would be confused if the mutable object modify members.

Copying a struct produces an object of its own type. If the original is an immutable SwA, then the copy is an immutable SwA. That's why the compiler cannot produce a mutable SwA automatically.

After the naming convention of arrays, objects can be copied by an explicit dup() function:

struct SwA {
    string[] strings;

    SwA dup() const @property
    {
        auto result = SwA(strings.dup);
        return result;
    }
}

(Apologies if the following is obvious after that.)

Now the following compiles:

    auto i = immutable(SwA)([ "a", "b" ]);
    SwA m = i.dup;

And as expected, the following would compile as well:

    m.strings ~= "c";

With that, to produce mutable elements from an array of immutable(SwA), we can use map() and if the mutable elements need not be put into an array eagerly, the following is sufficient:

    auto arr1 = [ immutable(SwA)([ "a", "b" ]),
                  immutable(SwA)([ "x", "y" ]) ];

    auto arr2 = arr1.map!(e => e.dup);
    writeln(arr2);

arr2 is a range of mutable SwA objects that can be passed to other range algorithms, even to writeln. (I am pretty sure the produced SwA objects are rvalues as they come out of map.)

The output is:

[SwA(["a", "b"]), SwA(["x", "y"])]

And of course, if needed, arr2 could have been an actual array by calling array():

import std.array;
// ...
    auto arr2 = arr1.map!(e => e.dup).array;

Here is the whole program:

struct SwA {
    string[] strings;

    SwA dup() const @property
    {
        auto result = SwA(strings.dup);
        return result;
    }
}

import std.stdio;
import std.algorithm;
import std.array;

void main()
{
    auto i = immutable(SwA)([ "a", "b" ]);
    SwA m = i.dup;
    m.strings ~= "c";

    auto arr1 = [ immutable(SwA)([ "a", "b" ]),
                  immutable(SwA)([ "x", "y" ]) ];

    auto arr2 = arr1.map!(e => e.dup);
    writeln(arr2);
}

Ali
October 28, 2012
Thank you for your detailed answer!

What I don't understand is the reason why you need the .dup in the first place.

Take a look at these two structs that now contain an int* and an int instead of string[].

struct SA {
    int i;
}

struct SB {
    int* i;
}

If you try to .dup an array of SA it will work and if you .dup an array of SB it will fail. I understand why it works this way, because in case of SB you would get an mutable reference of type int* out of immutable(int*), which would brake the type system. However the struct SwA from above does neither correspond to SA nor to SB, it's imo more like SC:

struct SC {
    immutable(int)* i;
}

Now a copy would do no harm, everything that was immutable in the source and is still accessable from the copy is immutable, too. Any reason (despite of implemenational issues) that this is not how it works?


October 29, 2012
On 10/28/2012 02:37 AM, Tobias Pankrath wrote:
> the struct
> SwA from above does neither correspond to SA nor to SB, it's imo more
> like SC:
>
> struct SC {
> immutable(int)* i;
> }

Just to confirm, the above indeed works:

struct SC {
    immutable(int)* i;
}

void main()
{
    immutable(SC)[] arr1;
    SC[] arr2 = arr1.dup;    // compiles
}

> Now a copy would do no harm, everything that was immutable in the source
> and is still accessable from the copy is immutable, too. Any reason
> (despite of implemenational issues) that this is not how it works?

Getting back to the original code, the issue boils down to whether we can copy imutable(string[]) to string[]:

import std.stdio;

struct SwA {
    string[] strings;
}

void main()
{
    immutable(SwA)[] arr1;
    writeln(typeid(arr1[0].strings));
}

The program prints the following:

immutable(immutable(immutable(char)[])[])

Translating the innermost definition as string:

immutable(immutable(string)[])

Let's remove the struct and look at a variable the same type as the member:

    immutable(string[]) imm = [ "a", "b" ];
    writeln(typeid(imm));

The typeid is the same:

immutable(immutable(immutable(char)[])[])

So we can concentrate on 'imm' for this exercise. This is the same compilation error:

    immutable(string[]) imm = [ "aaa", "bbb" ];
    string[] mut = imm;       // <-- compilation ERROR

If that compiled, then both 'imm' and 'mut' would be providing access to the same set of strings. But the problem is, 'mut' could replace those strings (note that it could not modify the characters of those strings, but it could replace the whole string):

    mut[0] = "hello";

That would effect 'imm' as well. ('imm' is the equivalent of SwA.strings from your original code.)

Ali

October 29, 2012
On Monday, 29 October 2012 at 06:19:36 UTC, Ali Çehreli wrote:
>
> Just to confirm, the above indeed works:
>
> struct SC {
>     immutable(int)* i;
> }
>
> void main()
> {
>     immutable(SC)[] arr1;
>     SC[] arr2 = arr1.dup;    // compiles
> }

Oh, I thought I've tried it.

> [snip] [Really detailed description here] [snip]

Thanks, now I've got it :-)
October 30, 2012
On 29/10/12 07:19, Ali Çehreli wrote:
> On 10/28/2012 02:37 AM, Tobias Pankrath wrote:
>  > the struct
>  > SwA from above does neither correspond to SA nor to SB, it's imo more
>  > like SC:
>  >
>  > struct SC {
>  > immutable(int)* i;
>  > }
>
> Just to confirm, the above indeed works:
>
> struct SC {
>      immutable(int)* i;
> }
>
> void main()
> {
>      immutable(SC)[] arr1;
>      SC[] arr2 = arr1.dup;    // compiles
> }
>
>  > Now a copy would do no harm, everything that was immutable in the source
>  > and is still accessable from the copy is immutable, too. Any reason
>  > (despite of implemenational issues) that this is not how it works?
>
> Getting back to the original code, the issue boils down to whether we
> can copy imutable(string[]) to string[]:
>
> import std.stdio;
>
> struct SwA {
>      string[] strings;
> }
>
> void main()
> {
>      immutable(SwA)[] arr1;
>      writeln(typeid(arr1[0].strings));
> }
>
> The program prints the following:
>
> immutable(immutable(immutable(char)[])[])
>
> Translating the innermost definition as string:
>
> immutable(immutable(string)[])
>
> Let's remove the struct and look at a variable the same type as the member:
>
>      immutable(string[]) imm = [ "a", "b" ];
>      writeln(typeid(imm));
>
> The typeid is the same:
>
> immutable(immutable(immutable(char)[])[])
>
> So we can concentrate on 'imm' for this exercise. This is the same
> compilation error:
>
>      immutable(string[]) imm = [ "aaa", "bbb" ];
>      string[] mut = imm;       // <-- compilation ERROR
>
> If that compiled, then both 'imm' and 'mut' would be providing access to
> the same set of strings. But the problem is, 'mut' could replace those
> strings (note that it could not modify the characters of those strings,
> but it could replace the whole string):
>
>      mut[0] = "hello";
>
> That would effect 'imm' as well. ('imm' is the equivalent of SwA.strings
> from your original code.)
>
> Ali

Awesome answer, it's these kinds of responses that make the D community so great.