Suggestion: Walter Bright, why not to implement multiple inheritance in D?

Burton Radons burton-radons at smocky.com
Sat Dec 2 07:27:30 PST 2006


Gregor Richards wrote:
> Now we'd like to make a class C derived from both A and B:
> class C(A, B).vtable =
> 0 -> ???
> 1 -> ???
> 2 -> (C's own functions)
> 
> Now can you see the problem? We can't simply dereference vtable[0] or 
> vtable[1] to get the proper function, because there are two different 
> classes, each with their own vtable[0] and vtable[1].
> 
> There are solutions to this (obviously, since C++ works), but they're 
> usually arcane and always inefficient.

The solutions to getting around MI when it's necessary are by their very 
exclusion from base language features always arcane, often inefficient, 
and almost always incomplete. The language-standard D solution is 
mixins, which I am certain are a bad idea even if they worked better 
than they do.

I don't have a perfect solution to the problem; there is none because 
it's asking two objects to exist in one space and we need to resolve 
that. But given that multiple inheritance is sometimes necessary, waving 
it off as "too complex" or "bad form" is impractical; like any type of 
suppression, that only leads to cracking and nasty leaks (mixins).

I have an idea. Let's try making this assertion: no type will have in 
its inheritance tree another type multiple times without being an abuse 
of multiple inheritance. Given this statement, can anyone find an 
exception, where MI is not just being used to alias an object to 
multiple types (and would therefore be better handled with a field)? 
Because if that statement is true, then the biggest problem in multiple 
inheritance can be ignored because all non-abusive usages of it would be 
resolvable into a clean array, no more difficult than single 
inheritance, except that methods that exist in more than one separate 
hierarchies should disappear without manual overloading. What I mean is:

     class A
     {
         void foo (int);
     }

     class B
     {
         void foo (float);
     }

     class C : A, B
     {
         void bar () { foo (16); } // Invalid because foo exists in both 
A and B. If we overloaded them, it would be like selecting between two 
functions from different modules. So for language consistency's sake, 
foo is not visible here.
     }

     class D : A, B
     {
         void foo (int x) { A.foo (x); }
         void foo (float x) { B.foo (x); }
         void bar () { foo (16); } // But this is okay.
     }

Or better yet:

     class E : A, B
     {
         alias A.foo;
         alias B.foo;
     }

The key is in not disallowing something just because it's difficult, but 
because it's wrong, always wrong, negative index on an array wrong. I'm 
inclined to think my assertion must be correct, but I can't quite 
discern the law ruling it. One way in which it is ALWAYS correct is with 
patterns like this:

     interface I
     {
         void foo ();
     }

     class A : I
     {
         void foo () { ... }
     }

     class B : I
     {
         void foo () { ... }
     }

     class C : A, B
     {
     }

This is because A and B's implementation of foo must be different, so 
the user is literally asking two objects to exist in one point at the 
same time, which is not logical. The inheritor cannot confidently select 
between them with any consistency (which is the fallback MI languages 
use if they are intelligent enough to notice the problem at all), 
because he cannot consistently know what either of those methods do (if 
the method's changed over time, or is written by another maintainer). 
Since this is an unsolvable ambiguity, it must be wrong to do this.

Gregor Richards wrote:
 > Addendum:
 > Incidentally, it's usually considered bad form to use multiple
 > inheritance in C++.

Yeah, but C++ programmers are programming in C++. The hell do they know 
about good language practices? ;-)



More information about the Digitalmars-d mailing list