array cast should be supported corrected

Steven Schveighoffer schveiguy at yahoo.com
Thu Aug 7 12:30:06 PDT 2008


"Robert Fraser" wrote
> Steven Schveighoffer Wrote:
>
>> "Robert Fraser" wrote
>> > Frank Benoit Wrote:
>> >
>> >> interface I{}
>> >> class C : I {}
>> >>
>> >> C[] carray = getInstance();
>> >> I[] iarray = carray; // compile error
>> >> I[] iarray = cast(I[])carray; // runtime error (1)
>> >>
>> >> // correct way:
>> >> I[] iarray = new I[carray.length];
>> >> foreach( idx, c; carray ){
>> >> iarray[idx] = c; // implicit cast
>> >> }
>> >>
>> >> I use a template for doing this, but this looks so ugly.
>> >> I[] iarray = arraycast!(I)(carray);
>> >>
>> >> I think the D compiler should call a runtime method in (1) to do the
>> >> cast in a loop, instead of doing a simple type change that is not
>> >> working correctly.
>> >
>> >
>> > The only way to do this is if the cast performed a copy. Consider:
>> >
>> > interface I { }
>> > class A : I { }
>> > class B : I { }
>> >
>> > A[] aarray;
>> > I[] iarray = aarray;
>> > iarray ~= new B();
>> > A a = aarray[0]; // you BROKE my TYPE SYSTEM -- this is an instance of 
>> > B
>> >
>> > When upcasting an array you NEED to copy; you cannot alias. And a cast
>> > should never make a copy, since that's unexpected behavior.
>>
>> Aliasing is possible.  The problem is that for interfaces, the pointer is
>> changed.  If you change I to be a class, not an interface, then your code
>> will compile (of course, it has the same problem as you have added a B to
>> the end of an A array, but in general, casting from A[] to B[] where A is
>> implicitly castable to B and both A and B use the same amount of space, 
>> is
>> allowed.  Except for Interfaces :) )
>
> If you can replace "I" with an abstract class and get it to
> compile right, that's a good old-fashioned bug. It doesn't matter if A and
> B are the same size (unless you're using scope classes which might lead to
> slicing), the problem is that the bits are being interpreted as different
> types than they are.
>
> // Note these definitions -- A and B are not type-compatible!
> class Base { }
> class A : Base { char[] str; }
> class B : Base { int x, int y; }
>
> A[] a_array;
> Base[] base_array = a_array;
> base_array ~= new B(5, 10);
> A a = a_array[0];
> writefln(a.str); // Segfault!

Actually, because of the way arrays work, this will cause a segfault only in 
release mode, because a_array is still 0-length (and null).  Without release 
mode, you get an array bounds exception

A better example would be:
A[] a_array;
a_array ~= new A;
Base[] base_array = a_array;
B b = new B;
b.x = b.y = 100;
base_array[0] = b;
A a = a_array[0]; // a now points to b
writefln(a.str); // Segfault!

>
> I don't have a D compiler on this computer so I can't test it out, but if
> the compiler can implicitly cast A[] to Base[], then this is a hole in the 
> type
> system. The issue with interfaces is tangential.

The issues are orthogonal.  When casting to an interface array O(n) copying 
is the only reasonable choice.  O(1) aliasing is OK for a base class if you 
are not planning on changing the existing elements.

> It's also extremely easy to overlook this error. Say you're passing an 
> array
> of A[] to a function that modifies an array of Base[] by possibly adding
> things. In a large system, it can be a tricky bug to track down since the
> error may not manifest itself until a while after it happens, and may not
> always be a segfault (if they're the same size & it's just the bits being
> interpreted as the wrong type, things could get very ugly -- it may not
> always just segfault).

This is not a common case (to pass in a abstract base array in which you 
plan on changing elements).  The common case is to use the base class array 
as a means of writing a common function that *uses* the array, but does not 
create elements in it.  For that, the cast is perfectly safe.  Or to use it 
as a co-variant return value.  I think the benefits of being able to cast 
this way outweigh the uncommon pitfalls.

I look at it no differently than doing:

class C {int x;}

C c;
c.x = 5; // segfault

But the interface thing is a performance question.  Whether or not to 
implicitly generate heap activity and run an O(n) algorithm by default is a 
completely different question.

-Steve 





More information about the Digitalmars-d mailing list