Bug 5498 – wrong common type deduction for array of classes
Status
RESOLVED
Resolution
FIXED
Severity
normal
Priority
P2
Component
dmd
Product
D
Version
D2
Platform
All
OS
All
Creation time
2011-01-28T06:59:00Z
Last change time
2015-06-09T05:10:40Z
Keywords
pull, rejects-valid
Assigned to
yebblies
Creator
denis.spir
Comments
Comment #0 by denis.spir — 2011-01-28T06:59:48Z
Hello,
Seems there is no way to create an array of elements which types are subtypes of a common supertype:
void test0 () {
auto t1 = new T1();
auto t2 = new T2();
T0[] array = [t1, t2];
}
==>
Error: cannot implicitly convert expression (t1) of type __trials__.T0 to __trials__.T2
Error: cannot implicitly convert expression ([(__error),t2]) of type T2[] to T0[]
The array value is obviously correct. But there is here no common compatible type between T1 & T2 that D could directly choose; instead, they have a common super-type, which is the one intended as array element type.
D logically does not take our specification of the wished common type, in the target part of the assignment, into account; since the source array must in any case first exist at all. And it does not try to guess a common supertype by climbing up the type hierarchy tree. This would be naughty-bug-prone since D could find a common type which is not the intended one, precisely in case of programmer error. And anyway Object would always be convenient, while this is certainly not the programmer intention in the general case.
The core issue is that the language must first create a valid array value, before any attempt to convert to any explicitely specified type (if ever this feature was implemented). For this reason, cast or to! applied on the array cannot help neither; the original array must first initially be correct according to D rules for literal:
T0[] array = cast(T0[])[t1, t2];
==>
Same error.
A workaround is to cast one of the elements, instead of the array, to the intended common type (*):
void test2 () {
auto t1 = new T1();
auto t2 = new T2();
T0[] array1 = [cast(T0)t1, t2]; // ok
T0[] array2 = [t1, cast(T0)t2]; // ok
}
But this trick raises a conceptual problem: what we mean is specifying the array literal's common type; what is in fact written is a cast of an element. Far to be obvious.
A library solution can be made via an "array-feeding" helper function; it uses a variadic argument to avoid the user writing an array literal:
void feed (T) (ref T[] array, T[] elements...) {
array ~= elements;
}
void test3 () {
auto t1 = new T1();
auto t2 = new T2();
T0[] array;
array.feed(t1, t2); // means: array = [t1,t2];
array.feed(t2, t1); // means: array ~= [t2,t1];
writeln(array);
}
==>
[arraydef.T1, arraydef.T2, arraydef.T2, arraydef.T1]
As shown, feed can also extend an existing array, replacing "~=" which fails for the same cause as "=". (Reason why I called the func "feed", not "init".) This is still a workaround, maybe a better one. Programmers need to know about the issue and the provided solution, thus this should be mentionned in good place in doc about arrays.
A true solution would require having a way to hint the compiler about the intended type, in literal syntax itself --a hint taken into account by the language before any initial array is created. Just like postfixes 'w' & 'd' for chars and strings. The best I could think at is, by analogy, postfixing the element type to the array literal:
T0[] array = [t1, t2]T0;
Not very nice :-(
Denis
(*) to! cannot be used at all because we hit another, unrelated, bug; namely one about non mutually exclusive template constraints:
T0[] array1 = [to!(T0)(t1), t2];
==>
/usr/include/d/dmd/phobos/std/conv.d(99): Error: template std.conv.toImpl(T,S) if (!implicitlyConverts!(S,T) && isSomeString!(T) && isInputRange!(Unqual!(S)) && isSomeChar!(ElementType!(S))) toImpl(T,S) if (!implicitlyConverts!(S,T) && isSomeString!(T) && isInputRange!(Unqual!(S)) && isSomeChar!(ElementType!(S))) matches more than one template declaration, /usr/include/d/dmd/phobos/std/conv.d(559):toImpl(Target,Source) if (implicitlyConverts!(Source,Target)) and /usr/include/d/dmd/phobos/std/conv.d(626):toImpl(T,S) if (is(S : Object) && is(T : Object))
Comment #1 by schveiguy — 2011-03-09T05:39:44Z
I just noticed, your example lacks definition of T0, T1 and T2.
Should be:
class T0 {}
class T1 : T0 {}
class T2 : T0 {}
Comment #2 by schveiguy — 2011-03-09T05:41:07Z
*** Issue 5723 has been marked as a duplicate of this issue. ***
Tested in git HEAD (db18e36a1c060fff056ecfa88cf792edc8ef8c1d), appears to have been fixed by the pull. Please reopen if there is still a problem.
(Note that bug #3543 has not been fully fixed yet, but this specific bug and the others associated with it appear to have been fixed.)