View mode: basic / threaded / horizontal-split · Log in · Help
August 02, 2007
Re: Overloading/Inheritance issue
Regan Heath Wrote:

> 
> It's truly odd that no-one seems to have a problem with it in C++.  It's 
> not as if C++ even has 'alias' so it cannot pull symbols in, you're left 
> re-implementing in the derived class.  Perhaps everyone just does that 
> without thinking about it.

After thinking about it, my opinion is that C++ coders tend to avoid implementing everything in objects using inheritance as opposed to languages which require using objects for everything (i.e. Java).  This tends to have many coders putting their "methods" in the global namespace instead of in a method, putting everything in the same scope.

and BTW, Walter in his previous posts rightly points out that the "using" keyword can be used inside a C++ class much like the "alias" keyword is used in a D class to achieve the same effect.

> 
> > However, I think it should be changed.  Not sure if it will, as it
> > seems there  is already a precedent.  In my opinion, the base class
> > should be examined if the derived class does not provide a suitable
> > match.  I can't see how this would cause too much of a performance
> > hit
> 
> Performance isn't the reason against the Java behaviour, here is Walters 
> explaination:
> 
> <quote Walter quoting Stroustrup>
> ...
> </quote>
> 
> So, as you can see it's not for performance reasons but rather to avoid 
> obscure bugs which when they manifest do so silently.
> 
> Regan

My argument is not against this, as you will note that in these examples, both base and derived classes can match the call by some sort of implicit conversion.  In the case where both the base class and the derived class  match the call, I could care less if the derived class was called instead of the base class.  Who can say what class documentation the coder was looking at when he wrote the call?  Both options seem equally likely.

I am proposing a change of behavior when the derived class CANNOT match the call.  At that point the compiler errors out instead of examining the base classes.  When the coder writes code where there is only one match, and that match is in a base class, it makes perfect sense that the coder explicitly wants to call the base class' method.  I see no reason to force the coder to explicitly call for the base class' method when there is no alternative in the derived class, the code is unambiguous.

I'd bet we'd see 0 obscure bugs if this change was made ;)  As I've mentioned, I've already seen one obscure bug caused by the current implementation...

-Steve
August 02, 2007
Re: Overloading/Inheritance issue
Steve Schveighoffer Wrote:

> Regan Heath Wrote:

> > 
> > Performance isn't the reason against the Java behaviour, here is Walters 
> > explaination:
> > 
> > <quote Walter quoting Stroustrup>
> > ...
> > </quote>
> > 
> > So, as you can see it's not for performance reasons but rather to avoid 
> > obscure bugs which when they manifest do so silently.
> > 
> > Regan
> 

Hm.. for some reason, I thought this would appear under the original thread.  In any case here is the missing quote from above:

<quote Walter quoting Stroustrup>

Stroustrup gives two examples (slightly modified here):

---------------------------------
class X1 { void f(int); }

// chain of derivations X(n) : X(n-1)

class X9: X8 { void f(double); }

void g(X9 p)
{
    p.f(1);    // X1.f or X9.f ?
}
-----------------------------------
His argument is that one can easilly miss an overload of f() somewhere in a
complex class heirarchy, and argues that one should not need to understand
everything about a class heirarchy in order to derive from it. The other
example involves operator=(), but since D doesn't allow overloading
operator=() instead I'll rewrite it as if it were a function that needs to
alter a class state, and a derived class written later that 'caches' a
computation on the derived state:

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; }
}

Now, imagine B were a complex class with a lot of stuff in it, and our
optimizing programmer missed the existence of set(int). Then, one has:

    long foo(B b)
    {
        b.set(3);
        return b.squareIt();
    }

and we have an obscure bug.

</quote>
August 03, 2007
Re: Overloading/Inheritance issue
Steve Schveighoffer wrote:
> Hm.. for some reason, I thought this would appear under the original
> thread.  In any case here is the missing quote from above:

The web interface has ... 'issues' on occasion.  I use Thunderbird 
myself.  Opera is also good.

Regan
August 03, 2007
Re: Overloading/Inheritance issue
Steve Schveighoffer wrote:
> Regan Heath Wrote:
>> So, as you can see it's not for performance reasons but rather to
>> avoid obscure bugs which when they manifest do so silently.
>
> My argument is not against this, as you will note that in these
> examples, both base and derived classes can match the call by some
> sort of implicit conversion.  In the case where both the base class
> and the derived class  match the call, I could care less if the
> derived class was called instead of the base class.

But, that's exactly the problem.

In fact example 2 shows that we currently have the undesirable and buggy 
behaviour in the current D implementation, eg.

import std.stdio;

class B
{    long x;
     void set(long i) { writefln("B::set(long)"); x = i; }
     void set(int i)  { writefln("B::set(int)"); x = i; }
     long squareIt()  { writefln("B::squareit()"); return x * x; }
}
class D : B
{
     long square;
     void set(long i) { writefln("D::set(long)"); B.set(i); square = x 
* x; }
     long squareIt()  { writefln("D::squareit()"); return square; }
}

long foo(B b)
{
    b.set(3);
    return b.squareIt();
}

void main()
{
    writefln(foo(new D));
}

Output:
B::set(int)
D::squareit()
0

In the above example the object is actually of type 'D' but the method 
is called from a reference to a 'B'.  The result is a call to 
B::set(int), instead of D::set(long) and then D::squareit() which fails 
utterly.

This is an 'obscure' bug because it is happening silently.

It seems example 2 either no longer behaves as it did when it was first 
posted (D has changed) or it was never a correct example for the problem.

> I am proposing a change of behavior when the derived class CANNOT
> match the call.  At that point the compiler errors out instead of
> examining the base classes.  When the coder writes code where there
> is only one match, and that match is in a base class, it makes
> perfect sense that the coder explicitly wants to call the base class'
> method.  I see no reason to force the coder to explicitly call for
> the base class' method when there is no alternative in the derived
> class, the code is unambiguous.

I see the distinction, and it is the case with your original example as 
you wanted it to call select() from the base class.

Interestingly, if the derived class "EpollSelector" had to conform to 
the interface "ISelector" that the base class "AbstractSelector" 
implements then the author of "EpollSelector" would have noticed and 
added the 'alias' and this would never have come up.

This change you suggest does violate D's current 'simple' lookup rules 
and go against the original reasoning of:

"His argument is that one can easilly miss an overload of f() somewhere 
in a complex class heirarchy, and argues that one should not need to 
understand everything about a class heirarchy in order to derive from it."

So, if you want to see this change you're going to have to post 
something convincing addressing this, probably with some real-life examples.

> I'd bet we'd see 0 obscure bugs if this change was made ;)   As I've
> mentioned, I've already seen one obscure bug caused by the current
> implementation...

Not to nit pick but is the bug you're referring to the problem you had 
with Tango's tango.io.selector.EPollSelector?  If so I wouldn't say that 
bug was 'oscure' because presumably it gave you an error on compile, as 
opposed to silently failing on some customers system which is what 
happens in the two examples given by Walter (including example 2 above 
which fails in D today).

Regan
August 03, 2007
Re: Overloading/Inheritance issue
Regan Heath Wrote:

> Steve Schveighoffer wrote:
>  > My argument is not against this, as you will note that in these
>  > examples, both base and derived classes can match the call by some
>  > sort of implicit conversion.  In the case where both the base class
>  > and the derived class  match the call, I could care less if the
>  > derived class was called instead of the base class.
> 
> But, that's exactly the problem.
> 
> In fact example 2 shows that we currently have the undesirable and buggy 
> behaviour in the current D implementation, eg.
> 

Oh, I understand what you are saying.  However, undesirable behavior is unavoidable no matter which way we go.  It all depends on what you expect and what the user of the class expects.  My thought is that you have to pick one way, and just tell people that's the way it is and they have to live with it.  I'm speaking only of the case where both base and derived class can match the call exactly or through some implicit conversion.

On second thought, you could pick a third option for behavior, and say that if the derived class has no explicit match, but has implicit matches, and the base class has explicit or implicit matches, the compiler should error out saying it doesn't know which one you intended.  That would mean even less obscure bugs, forcing the user of the class to pick the one they want by explicitly casting the arguments or the object.

> I see the distinction, and it is the case with your original example as 
> you wanted it to call select() from the base class.
> 
> Interestingly, if the derived class "EpollSelector" had to conform to 
> the interface "ISelector" that the base class "AbstractSelector" 
> implements then the author of "EpollSelector" would have noticed and 
> added the 'alias' and this would never have come up.

One of the problems is that you would most likely re-use the unit test code for AbstractSelector, which means you would cast the EPollSelector instance to the AbstractSelector test code, hiding the actual problem.  The only way to find this is for the author to write test code that specifically uses the derived class to call the base class' functions that he expects to still exist.  I think this is an unnecessary requirement for finding this problem, especially when there is no reason to want it the other way.

> 
> This change you suggest does violate D's current 'simple' lookup rules 
> and go against the original reasoning of:
> 
> "His argument is that one can easilly miss an overload of f() somewhere 
> in a complex class heirarchy, and argues that one should not need to 
> understand everything about a class heirarchy in order to derive from it."

So by this argument, Walter is saying that the desired behavior of the author is to only make available the overloaded methods that the derived class overrides?  I totally disagree.  Why would an author want to do this?  If it is to hide the other overloads, he has failed, because the user of the class can just cast to a base class in order to call the method they want.  If the author desires to hide this from the user of the class, then his class can be used in ways he didn't intend.  An author who overrides a class ignorant of all the functionality the class provides is IMHO a poor coder.  Even with your example, with the "squareIt" bug, I would never accept code like that as correct.  The argument above seems to be defending that type of behavior as desired.  In my opinion, a class should behave the same no matter how you look at it, i.e. how you cast it.

> 
> So, if you want to see this change you're going to have to post 
> something convincing addressing this, probably with some real-life examples.

I've posted one.  Evidently others have had similar problems.  I'm not sure I need any more examples, I think the fundamental argument against is flawed, at least in this specific case where the base class can be used but the derived class cannot.  I'm also leaning towards my further argument that the compiler should error out if the desired behavior of the coder is not obvious.

> 
>  > I'd bet we'd see 0 obscure bugs if this change was made ;)   As I've
>  > mentioned, I've already seen one obscure bug caused by the current
>  > implementation...
> 
> Not to nit pick but is the bug you're referring to the problem you had 
> with Tango's tango.io.selector.EPollSelector?  If so I wouldn't say that 
> bug was 'oscure' because presumably it gave you an error on compile, as 
> opposed to silently failing on some customers system which is what 
> happens in the two examples given by Walter (including example 2 above 
> which fails in D today).

It took me several hours of poking to figure out why the selector thing didn't compile, then several hours of writing test code to demonstrate the issue, researching whether this was desired behavior, and even then, I couldn't find any definite answer in the D spec, which led me to post this thread.  I'd say that's a pretty obscure problem :)  Not to mention that it's not MY bug.  The author probably still doesn't know there is a problem, so from his point of view, the bug is still at large without him noticing.

-Steve
August 04, 2007
Re: Overloading/Inheritance issue
Regan Heath wrote:
> In fact example 2 shows that we currently have the undesirable and buggy 
> behaviour in the current D implementation, eg.
> 
> import std.stdio;
> 
> class B
> {    long x;
>      void set(long i) { writefln("B::set(long)"); x = i; }
>      void set(int i)  { writefln("B::set(int)"); x = i; }
>      long squareIt()  { writefln("B::squareit()"); return x * x; }
> }
> class D : B
> {
>      long square;
>      void set(long i) { writefln("D::set(long)"); B.set(i); square = x * 
> x; }
>      long squareIt()  { writefln("D::squareit()"); return square; }
> }
> 
> long foo(B b)
> {
>     b.set(3);
>     return b.squareIt();
> }
> 
> void main()
> {
>     writefln(foo(new D));
> }
> 
> Output:
> B::set(int)
> D::squareit()
> 0
> 
> In the above example the object is actually of type 'D' but the method 
> is called from a reference to a 'B'.  The result is a call to 
> B::set(int), instead of D::set(long) and then D::squareit() which fails 
> utterly.
> 
> This is an 'obscure' bug because it is happening silently.

I agree that this example is a problem. There's no way to detect it at 
compile time, so it should throw a runtime exception. The way to 
accomplish that is to stuff D's vtbl[] entry for B.set(int) with a dummy 
function that throws the exception.
August 04, 2007
Re: Overloading/Inheritance issue
Walter Bright wrote:
> Regan Heath wrote:
>> In fact example 2 shows that we currently have the undesirable and 
>> buggy behaviour in the current D implementation, eg.
>>
>> import std.stdio;
>>
>> class B
>> {    long x;
>>      void set(long i) { writefln("B::set(long)"); x = i; }
>>      void set(int i)  { writefln("B::set(int)"); x = i; }
>>      long squareIt()  { writefln("B::squareit()"); return x * x; }
>> }
>> class D : B
>> {
>>      long square;
>>      void set(long i) { writefln("D::set(long)"); B.set(i); square = x 
>> * x; }
>>      long squareIt()  { writefln("D::squareit()"); return square; }
>> }
>>
>> long foo(B b)
>> {
>>     b.set(3);
>>     return b.squareIt();
>> }
>>
>> void main()
>> {
>>     writefln(foo(new D));
>> }
>>
>> Output:
>> B::set(int)
>> D::squareit()
>> 0
>>
>> In the above example the object is actually of type 'D' but the method 
>> is called from a reference to a 'B'.  The result is a call to 
>> B::set(int), instead of D::set(long) and then D::squareit() which 
>> fails utterly.
>>
>> This is an 'obscure' bug because it is happening silently.
> 
> I agree that this example is a problem. There's no way to detect it at 
> compile time, so it should throw a runtime exception. The way to 
> accomplish that is to stuff D's vtbl[] entry for B.set(int) with a dummy 
> function that throws the exception.

Certain C++ compilers have done something like this in the past, if I 
remember correctly.  I'd have to do some googling to remember the 
details, but I'm sure it was either the MS or Sun compiler.


Sean
August 04, 2007
Re: Overloading/Inheritance issue
Sean Kelly wrote:
> Walter Bright wrote:
>> I agree that this example is a problem. There's no way to detect it at 
>> compile time, so it should throw a runtime exception. The way to 
>> accomplish that is to stuff D's vtbl[] entry for B.set(int) with a 
>> dummy function that throws the exception.
> 
> Certain C++ compilers have done something like this in the past, if I 
> remember correctly.  I'd have to do some googling to remember the 
> details, but I'm sure it was either the MS or Sun compiler.

I didn't know that. This particular problem may not come up so often 
with C++ because functions are not virtual by default - but not virtual 
by default produces its own set of very hard to find bugs that I've 
wasted many hours tracking down.
August 04, 2007
Re: Overloading/Inheritance issue
Walter Bright wrote:
> Sean Kelly wrote:
>> Walter Bright wrote:
>>> I agree that this example is a problem. There's no way to detect it 
>>> at compile time, so it should throw a runtime exception. The way to 
>>> accomplish that is to stuff D's vtbl[] entry for B.set(int) with a 
>>> dummy function that throws the exception.
>>
>> Certain C++ compilers have done something like this in the past, if I 
>> remember correctly.  I'd have to do some googling to remember the 
>> details, but I'm sure it was either the MS or Sun compiler.
> 
> I didn't know that. This particular problem may not come up so often 
> with C++ because functions are not virtual by default - but not virtual 
> by default produces its own set of very hard to find bugs that I've 
> wasted many hours tracking down.

I stumbled across the information while trying to diagnose a bizarre 
linker error.  It turned out that the function that couldn't be found 
was the dummy routine.  And that reminds me which compiler it is--it's 
the MS one.  Their explanation made it seem like the situation the 
routine was there for wasn't likely to ever actually happen and I 
regarded it as a curiosity at the time.  It's kind of neat to see the 
idea resurface here though :-)


Sean
August 04, 2007
Re: Overloading/Inheritance issue
Walter Bright wrote:
> Regan Heath wrote:
>> In fact example 2 shows that we currently have the undesirable and 
>> buggy behaviour in the current D implementation, eg.
>>
>> import std.stdio;
>>
>> class B
>> {    long x;
>>      void set(long i) { writefln("B::set(long)"); x = i; }
>>      void set(int i)  { writefln("B::set(int)"); x = i; }
>>      long squareIt()  { writefln("B::squareit()"); return x * x; }
>> }
>> class D : B
>> {
>>      long square;
>>      void set(long i) { writefln("D::set(long)"); B.set(i); square = x 
>> * x; }
>>      long squareIt()  { writefln("D::squareit()"); return square; }
>> }
>>
>> long foo(B b)
>> {
>>     b.set(3);
>>     return b.squareIt();
>> }
>>
>> void main()
>> {
>>     writefln(foo(new D));
>> }
>>
>> Output:
>> B::set(int)
>> D::squareit()
>> 0
>>
>> In the above example the object is actually of type 'D' but the method 
>> is called from a reference to a 'B'.  The result is a call to 
>> B::set(int), instead of D::set(long) and then D::squareit() which 
>> fails utterly.
>>
>> This is an 'obscure' bug because it is happening silently.
> 
> I agree that this example is a problem. There's no way to detect it at 
> compile time, so it should throw a runtime exception. The way to 
> accomplish that is to stuff D's vtbl[] entry for B.set(int) with a dummy 
> function that throws the exception.

There is a problem the moment the D subclass doesn't override all 
overloads, so that can be detected at compile time and an error issued 
right there.


-- 
Bruno Medeiros - MSc in CS/E student
http://www.prowiki.org/wiki4d/wiki.cgi?BrunoMedeiros#D
« First   ‹ Prev
1 2
Top | Discussion index | About this forum | D home