Proposition for change in D regarding Inheriting overloaded methods

Regan Heath regan at netmail.co.nz
Tue Aug 7 11:27:41 PDT 2007


Walter Bright wrote:
> The next update of the compiler will throw a runtime exception for this 
> case.

So, in this case:

class B
{    long x;
      void set(long i) { x = i; }
     void set(int i) { x = i; }
     long squareIt() { return x * x; }
}
class D : B
{
     long square;
     void set(long i) { B.set(i); square = x * x; }
     long squareIt() { return square; }
}
long foo(B b)
{
     b.set(3);
     return b.squareIt();
}

when the call to b.set(3) is made you insert a runtime check which looks 
for methods called 'set' in <actual type of object>, if none of them 
take a <insert types of parameters> you throw an exception.

Is this done at runtime instead of compile time because the parameters 
cannot always be determined at compile time?

>> 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.
> 
> The problem with code that looks like a mistake, but the compiler makes 
> some assumption about it and compiles it anyway, is that the code 
> auditor cannot tell if it was intended behavior or a coding error. 
> Here's a simple example:
> 
> void foo()
> { int i;
>   ...
>   { int i;
>     ...
>     use i for something;
>   }
> }
> 
> To a code auditor, that shadowing declaration of i looks like a mistake, 
> because possibly the "use i for something" code was meant to refer to 
> the outer i, not the inner one. (This can happen when code gets updated 
> by multiple people.) To determine if it was an actual mistake, the code 
> auditor is in for some serious spelunking. This is why, in D, shadowing 
> declarations are illegal. It makes life easier for the auditor, because 
> code that looks like a mistake is not allowed.

It took me a while (because the example seems to be about something 
totally different) but I think the argument you're making is that you 
would prefer an error, requiring the author to specify what they want 
explicitly, rather than for the compiler to make a potentially incorrect 
assumption, silently. Is that correct?

In the original example (trimmed slightly):

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("hello");     // generates a compiler error
   a.foo("hello");     // calls A.foo(char[])
}

you're already making an assumption, you're assuming the author of B 
does not want to expose foo(char[]) and it's the fact that this 
assumption is wrong that has caused this entire debate.

As others have mentioned, this assumption destroys the "is-a" 
relationship of inheritance because "foo(char[])" is a method of A but 
not a method of B.  Meaning B "isn't-a" A any more... unless you've 
referring to a B with a reference to an A, when suddenly, it is.

Crazy idea, could the compiler (when it fails to match this overload) 
cast the object to it's base class and try again, repeat until you hit 
Object.  I guess this would essentially be a modification of the method 
lookup rules ;)


Making the opposite assumption (implicitly aliasing the "foo(char[])") 
doesn't introduce any silent bugs (that I am aware of) and restores the 
"is-a" relationship.

If the author really didn't want to expose "foo(char[])" then why were 
they deriving their class from A?  It goes against the whole idea of 
inheritance, doesn't it?

In special cases perhaps this is valid, in which case the author should 
explicitly define an overload and throw and exception or assert or both.

Note that I said "special cases" above, I think the most common case is 
that the "foo(char[])" should be implicitly aliased into the derived 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.
> 
> I am not comfortable with this method, as it will force the derived 
> class programmer to implement overloads that may not be at all meant to 
> exist in the API he defines for that class. I think he should be in full
> control of the API for the class, and not forced to provide 
> implementations of functions that may be irrelevant clutter. 

I agree, but I get the impression this was the least favoured 
suggestion, probably for the very reason you mention here.

>> 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.
> 
> I believe that the enforcing of the override attribute, and the runtime 
> exception case as described, closes all the known hijacking issues.

You're probably correct, but it doesn't solve the "compiler makes the 
wrong assumption" problem or the "irritating behaviour" problem ;)

Regan



More information about the Digitalmars-d mailing list