View mode: basic / threaded / horizontal-split · Log in · Help
August 19, 2006
How should overruling of overloaded functions work?
Ok, this issue is controversaly. The following explains the situation fine:

http://groups.google.com/group/alt.comp.lang.learn.c-c++/msg/2bdda463aed5d153?hl=en&lr=lang_en&ie=UTF-8&oe=UTF-8&safe=off

In short, should the following work (without using the alias hack):

class Base {
    void f() {...}
    void f(int) {...}
}

class Derived : Base {
    void f() {...}
}

void func() {
    Derived obj = new Derived;

    obj.f(10);  //doesn't work
}

There were/are good reason(s) why it should not work (see the link).
I personally think it should work, though. There are a lot of people that  
think likewise. Of course, the other half think that it shouldn't work.


This is why I think it should work:

1)
If you like to change the behavior of the function altogether, then you  
should change all the overload functions. If a new overload is later added  
to the Base class, then you have to add the corresponding function to the  
Derived class. That is a problem, but hiding of overloads of Base doesn't  
work either. Why? See the point 2.


2)
Using the Derived class via the Base class will access the overloads  
defined in Base. For example:

class Base {
    void f(int v) {printf("%d\n", v);}
    void f(float v) {printf("%f\n", v);}
}

class Derived : Base {
    void f(int v) {printf("%d\n", -v);}
}

void func(Base obj) {
    obj.f(1);     //prints -1
    obj.f(1.0);   //prints 1.0, not -1.0
}

void main() {
    Derived obj = new Derived;

    func(obj);
}

We want that Derived changes the behavior of 'f()' so that it'll print  
positive values as negative ones and vice versa. If 'f()' is used in  
'main', the hiding of Base's 'f(float)' will prevent it being called.  
However, this restriction does not apply to inside 'func()'. 'f()' works  
incorrectly here: it prints a positive value for a float, which is not  
wanted. The programmer of course wants that 'f()' works consistent  
_throughout_ the code.

When 'f(float)' is added to Base, the corresponding function must also be  
added to Derived.


3)
If the overloading does not work for derived classes, then the following  
common case does not work without the alias hack:

class String {...}

class Base {
    void f(String s) {...}
    //these functions are provided for convenience...
    final void f(int v) {
        f(new String(v));
        }
    final void f(float v) {
        f(new String(v));
        }
}

class Derived : Base {
    void f(String s) {...}  //overrule the main function that does all the  
work
}

'f(int)' and 'f(float)' are hidden from Derived. You have to use aliasing,  
or the following coding style:

class Base {
    final void f(String s) {
        do_f(s);
        }
    final void f(int v) {
        do_f(new String(v));
        }
    final void f(float v) {
        do_f(new String(v));
        }

    void do_f(String s) {...}
}

class Derived : Base {
    void do_f(String s) {...}
}


4)
Aliasing nullifies the 'safety net' provided by the language. (Hence the  
safety net does not work.)


5)
The hiding of overloads in Derived works only partially, which is not a  
good thing, I think. It should work completely or not at all. In addition,  
cases where the hiding is not desirable are far more common (IMHO).
August 20, 2006
Re: How should overruling of overloaded functions work?
This is the old problem of modifying the base class, while having the 
subclass continue to work as it should. It manifests itself in many ways.

The only way one can be sure to avoid this problem, is to never extend a 
class that one does not control -- i.e. classes defined in libraries etc.

If this rule is followed, there's indeed no reason for the current (C++ 
like) hiding of overloaded functions.

See for instance, "Why extends is evil" at JavaWorld, discussing the 
"fragile base-class" problem.
http://www.javaworld.com/javaworld/jw-08-2003/jw-0801-toolbox.html

Quote:
> I once attended a Java user group meeting where James Gosling
> (Java's inventor) was the featured speaker. During the memorable
> Q&A session, someone asked him: "If you could do Java over again,
> what would you change?" "I'd leave out classes," he replied.

It's a controversial topic. :-)

Søren J. Løvborg
web@kwi.dk
August 20, 2006
Re: How should overruling of overloaded functions work?
On Sun, 20 Aug 2006 14:54:29 +0300, Søren J. Løvborg <web@kwi.dk> wrote:
> This is the old problem of modifying the base class, while having the
> subclass continue to work as it should. It manifests itself in many ways.
>
> The only way one can be sure to avoid this problem, is to never extend a
> class that one does not control -- i.e. classes defined in libraries etc.
>
> If this rule is followed, there's indeed no reason for the current (C++
> like) hiding of overloaded functions.
>
> See for instance, "Why extends is evil" at JavaWorld, discussing the
> "fragile base-class" problem.
> http://www.javaworld.com/javaworld/jw-08-2003/jw-0801-toolbox.html
>
> Quote:
>> I once attended a Java user group meeting where James Gosling
>> (Java's inventor) was the featured speaker. During the memorable
>> Q&A session, someone asked him: "If you could do Java over again,
>> what would you change?" "I'd leave out classes," he replied.
>
> It's a controversial topic. :-)
>
> Søren J. Løvborg
> web@kwi.dk
>

Thanks for the link to the article, it was interesting. There were good  
points, although inheritance was designed so that you can re-use the old  
code, of course. Inheritance is a good thing, but it can also be a bad  
thing. I guess there are two sides in every programming style, rules, etc.
August 20, 2006
Re: How should overruling of overloaded functions work?
On Sat, 19 Aug 2006 18:09:25 +0300, Kristian <kjkilpi@gmail.com> wrote:
> 3)
> If the overloading does not work for derived classes, then the following  
> common case does not work without the alias hack:
>
> class String {...}
>
> class Base {
>      void f(String s) {...}
>      //these functions are provided for convenience...
>      final void f(int v) {
>          f(new String(v));
>          }
>      final void f(float v) {
>          f(new String(v));
>          }
> }
>
> class Derived : Base {
>      void f(String s) {...}  //overrule the main function that does all  
> the work
> }


Okey, okey, why doesn't someone tell me that this actually works!? :)

The functions 'f(int)' and 'f(float)' are not hidden from Derived because  
they're final. Nice!

This was the main reason I protested against the current overload function  
hiding. But because it do work, I will fell silent (and look a bit  
embarrassed). ;)

This enables me to write these convenience functions without the need of  
rewriting (or alias hacking) them in subclasses. Non-final functions  
should be reimplemented anyway (if someone wants to use alias hacking for  
those, be my guest).
August 20, 2006
Re: How should overruling of overloaded functions work?
Søren J. Løvborg wrote:
> See for instance, "Why extends is evil" at JavaWorld, discussing the 
> "fragile base-class" problem.
> http://www.javaworld.com/javaworld/jw-08-2003/jw-0801-toolbox.html

That article was horrible. It sounds plausible that safe changes in a base class 
might have undesired effects on a derived class but I would need an example. All 
his examples ignored the stubs of the base class. Big surprise ending -- he 
fixes the problem of ignoring the stubs of base classes with an interface.

He says "whenever I violate a central OO principle like *implementation hiding*, 
I end up rewriting that code." Yet the article is really about subtle variations 
of failing to hide implementation. So subtle in fact that the author did not 
even realize the problem:

  class ObviousProblem
  {
    public DataStore data;
  }

  class SubtleProblem extends DataStore
  {
    // does not override /any/ DataStore methods
  }

Now we have:

  ObviousProblem o = new ObviousProblem();
  o.data.invalidate();

  SubtleProblem  s =  new SubtleProblem();
  s.invalidate();

In short extending a class just because you are using it as a backing store is a 
bad idea. A big clue that is-a does not hold is when not a single one of your 
base class' methods make sense for the derived class. Yet despite adding and 
removing random elements makes no sense for a Stack his example is:

  class ArrayList
  {
    public void add( int i, Object o ) { }
    public Object remove( int i ) { }
    public void clear() { }
  }

  class Stack extends ArrayList
  {
    public void push( Object article )
    public Object pop()
    public void push_many( Object[] articles )
  }

Now translating that to a more obvious example an implementation hiding failure:

  class StackEquivalent
  {
    public ArrayList data;
    public void push( Object article )
    public Object pop()
    public void push_many( Object[] articles )
  }

Using /any/ of ArrayList's functions on a Stack object will invalidate it. Not 
just clear(). The /exact/ same situation applies to StackEquivalent.



Now the second implementation hiding failutre is not about exposing more than 
one needs but rather less:

  class Stack
  {
    public void push( Object article )
    public Object pop()
    public void push_many( Object[] articles )
  }

  class Monitorable_stack extends Stack
  {
      private int high_water_mark = 0;
      private int current_size;

      public void push( Object article )
      {   if( ++current_size > high_water_mark )
              high_water_mark = current_size;
          super.push(article);
      }

      public Object pop()
      {   --current_size;
          return super.pop();
      }

      public int maximum_size_so_far()
      {   return high_water_mark;
      }
  }

What jumps out when you just look at the function stubs for base and derived is 
that Monitorable_stack chose not to override push_many. It certainly is one of 
the functions you ought to be interested in intercepting.

The only plausible conclusion is that an inference has been made about Stack's 
hidden implementation. It turns out that was exactly the reasoning behind the 
article's author. Yet again has to violate "a central OO principle like 
implementation hiding" in order to make his point.

Given the pattern that all his examples ignored the stubs of the base class one 
can only conclude that he needs to use interfaces to be able to think clearly 
about what certain classes expose. If it works for him then I am happy he 
figured it out. However his stub blindness has nothing fundamental to do with 
inheritance. In short I suspect he has misinterpreted "The fragile base-class 
problem".
August 20, 2006
Re: How should overruling of overloaded functions work?
> What jumps out when you just look at the function stubs for base and 
> derived is that Monitorable_stack chose not to override push_many. It 
> certainly is one of the functions you ought to be interested in 
> intercepting.
>
> The only plausible conclusion is that an inference has been made about 
> Stack's hidden implementation. It turns out that was exactly the reasoning 
> behind the article's author. Yet again has to violate "a central OO 
> principle like implementation hiding" in order to make his point.

Imagine that push_many was added to the base class in a later version, after 
Stack was created. Then you have the exact same problem, without making any 
assumptions about the implementation as such. And the same problem is used 
to justify the hiding of overloaded methods in C++ (and hence, D), as 
discussed in the original post.

The difference between extending a base class and implementing an interface 
is that if you add a new method to the class, it can introduce subtle bugs, 
while if you add it to an interface, it's a compile-time error unless you 
also add it to every implementation of that interface.

The reason for extending a class is to override "hook" methods to change the 
functionality of the class, e.g. the paint() method of a GUI control, or the 
add() method of a list. However, when you do this, you no longer only have 
to worry about the "public interface" for the class (all the public methods, 
which users of the class may call), but also about keeping a consistent 
"protected interface".

In the public interface, the implementor simply marks the public methods 
with visibility "public", and he's done.

   /* Call this to add an object onto the stack. */
   public void push(Object o)

The contract for using the public interface is simple: Provide valid 
argument values, and the method will do what it's supposed to.

With the "protected interface", correct visibility is the least of the 
problems. The implementor needs to define exactly which methods may be 
overriden by subclasses (not neccessarily all of the public methods), and 
the exact semantics of each method, for instance:

   /* This is guaranteed to be called exactly once for every object added. 
*/
   public void push(Object o)

The contract for using the "protected interface" isn't simple. When 
subclassing, you need to read the documentation, or you'll introduce hard to 
find bugs. The contract cannot be enforced by the compiler, nor can it be 
enforced at runtime.

So yes, you can produce code that works perfectly, in which you extend 
classes (obviously -- everybody does it).
You can also produce code that works perfectly, in which the member 
variables of all classes are public.
By eliminating or minimizing both, you might save yourself some trouble. Or 
you might not.

I, personally, am not letting go of subclasses just yet...

Søren J. Løvborg
web@kwi.dk
August 21, 2006
Re: How should overruling of overloaded functions work?
Søren J. Løvborg wrote:
>> What jumps out when you just look at the function stubs for base and 
>> derived is that Monitorable_stack chose not to override push_many. It 
>> certainly is one of the functions you ought to be interested in 
>> intercepting.
>>
>> The only plausible conclusion is that an inference has been made about 
>> Stack's hidden implementation. It turns out that was exactly the reasoning 
>> behind the article's author. Yet again has to violate "a central OO 
>> principle like implementation hiding" in order to make his point.
> 
> Imagine that push_many was added to the base class in a later version, after 
> Stack was created. Then you have the exact same problem, without making any 
> assumptions about the implementation as such. And the same problem is used 
> to justify the hiding of overloaded methods in C++ (and hence, D), as 
> discussed in the original post.
> 
> The difference between extending a base class and implementing an interface 
> is that if you add a new method to the class, it can introduce subtle bugs, 
> while if you add it to an interface, it's a compile-time error unless you 
> also add it to every implementation of that interface.
> 
> The reason for extending a class is to override "hook" methods to change the 
> functionality of the class, e.g. the paint() method of a GUI control, or the 
> add() method of a list. However, when you do this, you no longer only have 
> to worry about the "public interface" for the class (all the public methods, 
> which users of the class may call), but also about keeping a consistent 
> "protected interface".
> 
> In the public interface, the implementor simply marks the public methods 
> with visibility "public", and he's done.
> 
>     /* Call this to add an object onto the stack. */
>     public void push(Object o)
> 
> The contract for using the public interface is simple: Provide valid 
> argument values, and the method will do what it's supposed to.
> 
> With the "protected interface", correct visibility is the least of the 
> problems. The implementor needs to define exactly which methods may be 
> overriden by subclasses (not neccessarily all of the public methods), and 
> the exact semantics of each method, for instance:
> 
>     /* This is guaranteed to be called exactly once for every object added. 
> */
>     public void push(Object o)
> 
> The contract for using the "protected interface" isn't simple. When 
> subclassing, you need to read the documentation, or you'll introduce hard to 
> find bugs. The contract cannot be enforced by the compiler, nor can it be 
> enforced at runtime.
> 
> So yes, you can produce code that works perfectly, in which you extend 
> classes (obviously -- everybody does it).
> You can also produce code that works perfectly, in which the member 
> variables of all classes are public.
> By eliminating or minimizing both, you might save yourself some trouble. Or 
> you might not.
> 
> I, personally, am not letting go of subclasses just yet...
> 
> Søren J. Løvborg
> web@kwi.dk 
> 
> 

Thanks for the eloquent and well thought out reply. I realized I was out of line 
with my criticism of his second example. From this article a damning quote:

> Bill Venners:
>
> In Effective Java, Bloch offered a good example in which the addAll method
> in class HashSet calls add on itself. A subclass attempts to track the total
> number of objects added to the set by counting the objects passed to both
> addAll and add, but it doesn't work as expected because of addAll's self-use.
> If someone passes three objects to addAll, the total number of objects
> increments by six, not three, because addAll calls add three times.
>
> http://www.artima.com/intv/issues2.html

In the previous example he only counts in the single add case but in this 
example counting in both cases is incorrect. The correct behavior seems to be 
only possible either by violating the implementation hiding and peeking or 
somehow getting Stack to also grow a length() function magically!

I would certainly quantify this combination of factors as one nasty example of 
safe changes in a base class having undesired effects on a derived class. 
Certainly as you suggested in your first post adding a length function magically 
is actually pretty easy if you only subclass your own classes.

I am also left wondering how often this concern actually manifests itself. Is it 
purely theory with no practice? Would Sun silently slip in something like an 
addAll function to HashSet later? How much has the C library changed over time? 
How relevent is C's past to the future of more recent languages?
August 21, 2006
Re: How should overruling of overloaded functions work?
> I am also left wondering how often this concern actually manifests itself. 
> Is it purely theory with no practice? Would Sun silently slip in something 
> like an addAll function to HashSet later? How much has the C library 
> changed over time? How relevent is C's past to the future of more recent 
> languages?

Well, as of Java 1.2, with the introduction of the unified Collection 
framework, the addElement() method of Vector was superseded by a new method, 
add().

addElement() stays, however, for backwards compatibility, and isn't even 
deprecated (for reasons only known to Sun...)

Unless add() simply redirects to addElement(), old code that only overrides 
addElement() will be broken.

A quick test (with Sun's Java 1.6 beta) shows that in fact, neither method 
calls the other one. Old code could indeed be broken by this.

So, yes, such library changes happens. Which I guess is sort of an argument 
for retaining the C++ overload hiding rules. (Yet, I still think they're 
unneccessary.)

Søren J. Løvborg
web@kwi.dk
August 22, 2006
Re: How should overruling of overloaded functions work?
Kristian wrote:
> On Sat, 19 Aug 2006 18:09:25 +0300, Kristian <kjkilpi@gmail.com> wrote:
>> 3)
>> If the overloading does not work for derived classes, then the 
>> following common case does not work without the alias hack:
>>
>> class String {...}
>>
>> class Base {
>>      void f(String s) {...}
>>      //these functions are provided for convenience...
>>      final void f(int v) {
>>          f(new String(v));
>>          }
>>      final void f(float v) {
>>          f(new String(v));
>>          }
>> }
>>
>> class Derived : Base {
>>      void f(String s) {...}  //overrule the main function that does 
>> all the work
>> }
> 
> 
> Okey, okey, why doesn't someone tell me that this actually works!? :)
> 
> The functions 'f(int)' and 'f(float)' are not hidden from Derived 
> because they're final. Nice!
> 
> This was the main reason I protested against the current overload 
> function hiding. But because it do work, I will fell silent (and look a 
> bit embarrassed). ;)
> 
> This enables me to write these convenience functions without the need of 
> rewriting (or alias hacking) them in subclasses. Non-final functions 
> should be reimplemented anyway (if someone wants to use alias hacking 
> for those, be my guest).

Are you sure it works? It's not working for me, I tried the following code:

-----
import std.stdio;

class String {}

class Base {
    void f(String s) {
    	writefln("Base.f(String)");
    }
    //these functions are provided for convenience...
    final void f(int v) {
        writefln("Base.f(int)");
    }
    final void f(float v) {
        writefln("Base.f(float)");
    }
}

class Derived : Base {
    void f(String s) { writefln("Derived.f"); }
}

void test() {
    Derived obj = new Derived;
    obj.f(10);  // ERROR, doesn't work
}
August 22, 2006
Re: How should overruling of overloaded functions work?
OOOKAY! You're right, that does not work! :( :( :(

There was a stupid error in my test case... and I was too quick to get out  
of my house running and yelling "it works!" to people! (When police  
appeared, I was forced to stop.)

One word: aaargh!
It was too good to be true anyway... :(

I just don't get it.
This is clearly a case where overload functions should not be hidden! I  
mean, if a function is final, you have to be absolutely sure that there is  
no need to change (i.e. override) it in a subclass.

If you cannot change it in a subclass, then there is no harm in changing  
its implementation in the base class. Subclasses will work as normal after  
the change. If not, then the function must not be final!


Thank you Walter, and others, for this great language.
... But *please* don't left these kind of glitches in it! Little details  
are important also, programmers have to deal with them all the time!


On Tue, 22 Aug 2006 17:15:56 +0300, Bruno Medeiros  
<brunodomedeiros+spam@com.gmail> wrote:
> Kristian wrote:
>> On Sat, 19 Aug 2006 18:09:25 +0300, Kristian <kjkilpi@gmail.com> wrote:
>>> 3)
>>> If the overloading does not work for derived classes, then the  
>>> following common case does not work without the alias hack:
>>>
>>> class String {...}
>>>
>>> class Base {
>>>      void f(String s) {...}
>>>      //these functions are provided for convenience...
>>>      final void f(int v) {
>>>          f(new String(v));
>>>          }
>>>      final void f(float v) {
>>>          f(new String(v));
>>>          }
>>> }
>>>
>>> class Derived : Base {
>>>      void f(String s) {...}  //overrule the main function that does  
>>> all the work
>>> }
>>   Okey, okey, why doesn't someone tell me that this actually works!? :)
>>  The functions 'f(int)' and 'f(float)' are not hidden from Derived  
>> because they're final. Nice!
>>  This was the main reason I protested against the current overload  
>> function hiding. But because it do work, I will fell silent (and look a  
>> bit embarrassed). ;)
>>  This enables me to write these convenience functions without the need  
>> of rewriting (or alias hacking) them in subclasses. Non-final functions  
>> should be reimplemented anyway (if someone wants to use alias hacking  
>> for those, be my guest).
>
> Are you sure it works? It's not working for me, I tried the following  
> code:
>
> -----
> import std.stdio;
>
> class String {}
>
> class Base {
>      void f(String s) {
>      	writefln("Base.f(String)");
>      }
>      //these functions are provided for convenience...
>      final void f(int v) {
>          writefln("Base.f(int)");
>      }
>      final void f(float v) {
>          writefln("Base.f(float)");
>      }
> }
>
> class Derived : Base {
>      void f(String s) { writefln("Derived.f"); }
> }
>
> void test() {
>      Derived obj = new Derived;
>      obj.f(10);  // ERROR, doesn't work
> }
>
« First   ‹ Prev
1 2
Top | Discussion index | About this forum | D home