Thread overview
SubClass[] does not implicitly convert to SuperClass[], why?
Feb 20, 2015
Tobias Pankrath
Feb 20, 2015
rumbu
Feb 20, 2015
Tobias Pankrath
Feb 20, 2015
Jonathan M Davis
Feb 20, 2015
rumbu
Feb 20, 2015
ketmar
February 20, 2015
What's the reason behind this design?

class Super {}
class Sub : Super {}

void foo(Super[] sup) {}

void main() {
    Sub[] array;
    foo(array); // error, cannot call foo(Super[]) with arguments (Sub[])
}
February 20, 2015
On Friday, 20 February 2015 at 07:57:17 UTC, Tobias Pankrath wrote:
> What's the reason behind this design?
>
> class Super {}
> class Sub : Super {}
>
> void foo(Super[] sup) {}
>
> void main() {
>     Sub[] array;
>     foo(array); // error, cannot call foo(Super[]) with arguments (Sub[])
> }

Just make the sup parameter const:

void foo(in Super[] sup) {}

http://dlang.org/arrays.html (end of the page):

A dynamic array T[] can be implicitly converted to one of the following:
const(U)[]
const(U[])
Where U is a base class of T.

The reson behind the design - I wonder about that also.
February 20, 2015
On Friday, 20 February 2015 at 08:25:49 UTC, rumbu wrote:
> On Friday, 20 February 2015 at 07:57:17 UTC, Tobias Pankrath wrote:
>> What's the reason behind this design?
>>
>> class Super {}
>> class Sub : Super {}
>>
>> void foo(Super[] sup) {}
>>
>> void main() {
>>    Sub[] array;
>>    foo(array); // error, cannot call foo(Super[]) with arguments (Sub[])
>> }
>
> Just make the sup parameter const:
>
> void foo(in Super[] sup) {}
>
> http://dlang.org/arrays.html (end of the page):
>
> A dynamic array T[] can be implicitly converted to one of the following:
> const(U)[]
> const(U[])
> Where U is a base class of T.
>
> The reson behind the design - I wonder about that also.

Thanks, didn't know that. Makes sense.

Probably the reason is:

void foo(Super[] sup) { sup[3] = new AnotherDerivedClass(); }

After foo returns the slice passed as argument would violate its invariant.
February 20, 2015
On Friday, February 20, 2015 08:25:48 rumbu via Digitalmars-d-learn wrote:
> On Friday, 20 February 2015 at 07:57:17 UTC, Tobias Pankrath wrote:
> > What's the reason behind this design?
> >
> > class Super {}
> > class Sub : Super {}
> >
> > void foo(Super[] sup) {}
> >
> > void main() {
> >     Sub[] array;
> >     foo(array); // error, cannot call foo(Super[]) with
> > arguments (Sub[])
> > }
>
> Just make the sup parameter const:
>
> void foo(in Super[] sup) {}
>
> http://dlang.org/arrays.html (end of the page):
>
> A dynamic array T[] can be implicitly converted to one of the
> following:
> const(U)[]
> const(U[])
> Where U is a base class of T.
>
> The reson behind the design - I wonder about that also.

Well, if it were legal, you could do something like

class Super {}
class Sub : Super {}
class Sub2 : Super {}

Sub[] subArr = createSubArr();
Super[] superArr = subArr;
superArr[2] = new Sub2;

Now, all of a sudden, subArr[2] is a Sub2, when a Sub2 is not a Sub. So, you would have broken the type system. The issue is called array covariance. const avoids the problem, because you can't assign any of the elements of the array.

A similar question was asked an answered previously on SO:

http://stackoverflow.com/questions/18158106/array-of-concrete-class-not-covariant-with-array-of-interface

though it looks like at the time that the questioned was answered, the conversion didn't work with const, so things have improved since then. But it should never work with mutable arrays or the type system would be violated.

- Jonathan M Davis

February 20, 2015
On Friday, 20 February 2015 at 10:41:04 UTC, Jonathan M Davis wrote:
>
> class Super {}
> class Sub : Super {}
> class Sub2 : Super {}
>
> Sub[] subArr = createSubArr();
> Super[] superArr = subArr;
> superArr[2] = new Sub2;
>
> Now, all of a sudden, subArr[2] is a Sub2, when a Sub2 is not a Sub. So, you
> would have broken the type system. The issue is called array covariance.
> const avoids the problem, because you can't assign any of the elements of
> the array.
>

A simple cast will always permit to destroy the type system entirely:

class Super {}

class Sub : Super {
	string alpha() { return "I am Sub"; }
	void SomeMethodNotFoundInSub2() { }
}

class Sub2: Super {
	string beta() { return "I am Sub2"; }
}

void foo(in Super[] sup)
{
	cast(Super)sup[0] = new Sub();
	cast(Super)sup[1] = new Sub2();
}


int main(string[] argv)
{

	Sub[] subArr = new Sub[2];
	foo(subArr);

	writeln(subArr[0]);
	writeln(subArr[1]); //look ma', my Sub[] array contains Sub2 elements

	writeln(subArr[0].alpha());
	writeln(subArr[1].alpha()); //look ma', I'm calling beta of Sub2, because it has the same vtbl offset as alpha in Sub

	subArr[0].SomeMethodNotFoundInSub2(); //ok
	subArr[1].SomeMethodNotFoundInSub2(); // now we have an AccesViolation or maybe not, depending on Sub2 contents.
	
	getchar();
    return 0;
}

The problem is in fact the line below, and that kind of assignment must be checked at runtime instead of limiting compile time features that can be anyway circumvented, by throwing a specific exception.

sup[1] = new Sub2();




February 20, 2015
On Fri, 20 Feb 2015 12:58:01 +0000, rumbu wrote:

> A simple cast will always permit to destroy the type system entirely:

`cast` is the thing programmer does explicitly. it's assumed that programmer is know what he is doing, and he takes full responsibility for that.

hidden type system breakage is completely different thing.

moving checks to runtime turns language to JS.