Proposition for change in D regarding Inheriting overloaded methods
Steven Schveighoffer
schveiguy at yahoo.com
Tue Aug 7 08:43:08 PDT 2007
OK,
So I originally posted the Overloading/Inheritance question as I was
confused by the behavior of the current D compiler. After reading all of
the responses arguments, I believe there are two main camps in the debate.
One camp, which I'll name the C++ camp, believes the C++ behavior is the
best approach. This camp includes Walter, and believes the current
implementation of the D compiler (which mimics C++) is best. The second
camp includes myself, and several other users of D who were either aware of
this issue, or unaware and surprised by the current implementation. I'll
call this the Java camp, as the behavior I desire is most closely imitating
Java (although not exactly). There may be other ways of solving this
problem, and I welcome those to voice their opinions. I will try to respond
to everyone.
Now, I am by no means an expert in anything to do with writing languages, so
this proposal may come across as a bit stumbly or informal, but I think it's
important that I start a new thread in which to sort of draw attention to
the fact that I am no longer asking questions about the current
implementation, nor am I submitting a bug. What I believe is that the
specification itself should be changed. So I'll make my proposal, and let
everyone attempt to shoot holes in it/bolster it, until hopefully there is a
decision by those important enough to make the changes on whether a change
should be made.
For those of you who were confused by the original question, I'll describe
the behavior of D as it stands, and why I have issues with it.
When a class inherits from another class, and redefines one of the base
class' methods, using the same name and parameter types, the derived class
overrides that method. However, if the method is overloaded, the base
class' methods are not considered when calling that method. This is the
case even when the derived class has no suitable overrides for the call in
question. For example (augmented example from spec):
class A
{
int foo(int x) { ... }
int foo(long y) { ... }
int foo(char[] s) { ... }
}
class B : A
{
override int foo(long x) { ... }
}
void test()
{
B b = new B();
A a = b;
b.foo(1); // calls B.foo(long), since A.foo(int) not considered
a.foo(1); // calls A.foo(int) because it is not overrided by B
b.foo("hello"); // generates a compiler error because A is not
considered for overrides
a.foo("hello"); // calls A.foo(char[])
}
To have the compiler consider the base class' overloads before considering
the derived class' overloads, an alias can be added:
class B : A
{
alias A.foo foo;
override int foo(long x) { ... }
}
void test()
{
B b = new B();
A a = b;
b.foo(1); // calls A.foo(int)
a.foo(1); // calls A.foo(int)
b.foo("hello"); // calls A.foo(char[])
a.foo("hello"); // calls A.foo(char[])
}
Thus ends the definition of the issue. Here is where my opinion comes in.
There are two issues in this scenario, and both of them have to do with the
intentions of the author of class B. I think everyone agrees that the
author of B intended to handle the case where foo(long) is called.
However, does the author intend to handle the case where foo(int) is called?
Let's assume he does (which is what the current compiler assumes). The
author has not forbidden the user of the class from calling A.foo, because
the user can simply cast to an A object, and call A.foo(int) directly.
Therefore, if the author meant to override foo(int) by defining just a
foo(long), he has failed. From these points, I believe that the above code
is an example of an incorrectly implemented override, and I believe that the
correct response the compiler should have is to error out, not while
compiling test(), but while compiling B, indicating to the user that he
should either declare the alias, or override foo(int). This is the point in
which my solution differs from Java. Java would allow this to proceed, and
call the base class' foo(int) in all cases, which is not what the author
intended.
The second issue is how to handle the foo(char[]) case. In the current
implementation, because A is not searched for overrides, the compiler
produces an error indicating that the user tried to call the foo(long)
method, but there is no implicit conversion from char[] to long. There are
two possibilities. One is that the author did not notice that A.foo(char[])
existed, and intended to override all instances of foo() with his foo(long)
override. However, the author is NOT notified of this issue, only the user
of the class is notified of this issue. So the potential for unambiguous
code to have escaped exists. The second possibility is that the author
fully intended to allow the base class to define foo(char[]), but forgot to
define the alias. Again, since the compiler gives no error, he is unaware
that he is releasing buggy code to the world. I believe the correct
assumption of the compiler should be that the user wanted the alias for the
base class' foo(char[]), and should alias it implicitly if and only if no
suitable match exists on the derived class. In the case where the author
did not notice foo(char[]) existed, he problably doesn't mind that
foo(char[]) is defined by the base class. If a suitable match exists that
is not a direct override of the base class, then the issue reduces to the
previous case, where an implicit conversion is required, and the compiler
should error out.
There is one other possibile solution that I would be willing to concede to,
and that is that the compiler errors out if the base class does not override
all overloads of a particular method name. This forces the user to either
override all overloads of the method, or define the alias. This would be
the safest solution, as the author of B must make his intentions perfectly
clear.
So my proposal is to change the specification so that:
If there is a class A, which is a base class of class B, where A defines a
method foo(args), and B defines a method foo(args2), such that the types of
args2 cannot be implicitly converted to args, and B does not define
foo(args), then the definition of B.foo(args) shall be implicitly aliased to
A.foo(args). If, using the same assumptions, args2 can be implicitly
converted to args, then the compiler should fail to compile B indicating
that the user must define B.foo(args) by override or by alias.
I believe this will give us the best of both camps, and allow much less code
to be released with silent bugs than the current implementation.
Let the hole shooting begin...
-Steve
More information about the Digitalmars-d
mailing list