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