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