My question is the same as this old one, but I do not yet understand the answer given: Diamond Problem
In the diamond problem, D inherits from B and C which both inherit from A, and B and C both override a method foo in A.
Suppose instead a triangle: there is no A, D inherits from B and C, which both implement a method foo.
As I understand, without special handling, the following would be ambiguous in either the diamond or triangle because it is not clear which foo to call:
D d = new D;
d.foo();
So I'm still not sure what makes this a diamond problem and not a more general multiple inheritance problem. It seems like you would need to provide some way to disambiguate this even in the "triangle" problem.
As I alluded to in the comments, some of the issues relate to how dynamic dispatch is typically implemented.
Assuming a vtable approach, any particular type must be able to produce a vtable that allows it to be treated as itself or any of its supertypes. Under single inheritance, this can be really easily implemented since each type's vtable can start with the same vtable layout as its immediate supertype followed by any new members that it introduces.
E.g. If B
has two methods
vtable_B
Slot # Method
1 B.foo
2 B.bar
And D
inherits from B
, overrides bar
and introduces baz
:
vtable_SI_D
Slot # Method
1 B.foo
2 D.bar
3 D.baz
Since D
didn't override foo
, it just copies whatever entry it finds in B
s vtable for slot #1.
Then any code working with a D
through a B
variable will only ever use slots #1 and #2 and everything works fine.
Introduce multiple inheritance, however, and you may not be able to use a single vtable. Assume we now introduce C
which also has foo
and bar
methods. Now we'll need to use different vtables when D
is cast to B
:
vtable_MI_D_as_B
Slot # Method
1 B.foo
2 D.bar
or to C
:
vtable_MI_D_as_C
Slot # Method
1 C.foo
2 D.bar
These are unambiguous1. The issue is trying to fill in the vtable for D
when its not cast to anything:
Slot # Method
1 <what goes here>
2 D.bar
3 D.baz
So, you're correct that the triangle inheritance does raise some issues. But since we're using a different vtable for D
as D
(as opposed to D
as B
or C
) we could simply omit an entry for Slot #1 and make it illegal to call D.foo
(in the simple case that nothing further is stated in D
s definition such as to use B
s foo
or overriding foo
):
vtable_MI_D
Slot # Method
2 D.bar
3 D.baz
Let's now introduce A
and have it define foo
, back to the classic diamond pattern. So A
s vtable is:
vtable_A
Slot # Method
1 A.foo
B
and C
are as described above. We can follow exactly the same approach above for D
, except for one additional problem. We have to supply a vtable for D
cast as A
. We can't just omit slot #1 - code dealing with an A
expects to be able to call foo
. And we can't just copy the entry from B
or C
's vtable since they have different values and they're both immediate supertypes.
This, I believe, is the gist of why the diamond pattern is typically used - because we can't just implement a "you can't call foo
on a D
" rule and be done with it.
1It's also worth observing here that slots #1 and #2 in the vtable_MI_D_as_B
and vtable_MI_D_as_C
vtables are completely unrelated. C
could have had slot #2 be for its foo
method and slot #6 for its bar
method. Method with the same names won't necessarily share the "same" slots.
This is in contrast with the later discussion of the diamond inheritance pattern where slot #1 really is the same slot across all types.