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