Eiffel Liberty -> Eiffel -> Ian Joyner -> Overloaded Functions

[3rd edition of the C++ Critique is now available ``including comparisons with Java and Eiffel''.. Ian Joyner (4 Nov 96)]
(Banner provided as an OO Community Service.)

Ian Joyner
ijoyner@acm.org

Overloaded functions are different to polymorphic functions: for each invocation the correct function is selected at compile time; with polymorphic functions, the correct function is bound dynamically at run-time. Polymorphism is achieved by redefining or overriding routines. Be careful not to confuse overriding and overloading.

Function Overloading

C++ allows functions to be overloaded if the arguments in the signature are different types. Overloaded functions are different to polymorphic functions: for each invocation the correct function is selected at compile time; with polymorphic functions, the correct function is bound dynamically at run-time. Polymorphism is achieved by redefining or overriding routines. Be careful not to confuse overriding and overloading. Overloading arises when two or more functions share a name. These are disambiguated by the number and types of the arguments. Overloading is different to multiple dispatching in CLOS, as multiple dispatching on argument types is done dynamically at run-time.

[Reade 89] points out the difference between overloading and polymorphism.

The qualification mechanism for overloaded functions is the function signature. Overloading can be useful as these examples show:

   max (int, int);
   max (real, real);

This will ensure that the best max routine for the types int and real will be invoked. Object-oriented programming, however, provides a variant on this. Since the object is passed to the routine as a hidden parameter (`this' in C++), an equivalent but more restricted form is already implicitly included in object-oriented concepts. A simple example such as the above would be expressed as:

   int  i, j; 
   real r, s;
   i.max (j);
   r.max (s);

but i.max (r) and r.max (j) result in compilation errors because the types of the arguments do not agree. By operator overloading of course, these can be better expressed, i max j and r max s, but min and max are peculiar functions that could accept two or more parameters of the same type so they can be applied to a arbitrarily sized list. So the most general code in Eiffel style syntax will be something like:

   il : COMPARABLE_LIST [INTEGER]
   rl : COMPARABLE_LIST [REAL]
   i := il.max 
   r := rl.max

The above examples show that the object-oriented paradigm, particularly with genericity can achieve function overloading, without the need for the function overloading of C++. C++, however, does make the notion more general. The advantage is that more than one parameter can overload a function, not just the implicit current object parameter.

Another factor to consider is that overloading is resolved at compile time, but overriding at run-time, so it looks as if overloading has a performance advantage. However, global analysis can determine whether the min and max functions are at the end of the inheritance line, and therefore can call them directly. That is, the compiler examines the objects i and r, looks at their corresponding max function, sees that at that point no polymorphism is involved, and so generates a direct call to max . By contrast, if the object was n which was defined to be a NUMBER which provided the abstract max function from which REAL.max and INTEGER.max were derived, then the compiler would need to generate a dynamically bound call, as n could refer to either a INTEGER or a REAL.

If it is felt that C++'s scheme of having parameters of different types is useful, it should be realised that object-oriented programming provides this in a more restricted and disciplined form. This is done by specifying that the parameter needs to conform to a base class. Any parameter passed to the routine can only be a type of the base class, or a subclass of the base class. For example:

   A.f (B someB) {...};
   class B ...;
   class D : public B ... 
   A a;
   D d;
   a.f (d);
The entity `d' must conform to the class `B', and the compiler checks this. The alternative to function overloading by signature, is to require functions with different signatures to have different names. Names should be the basis of distinction of entities. The compiler can cross check that the parameters supplied are correct for the given routine name. This also results in better self-documented software. It is often difficult to choose appropriate names for entities, but it is well worth the effort. [Wiener 95] contributes a nice example on the hazards of virtual functions with overloading:

   class Parent
   { 
      public: virtual int doIt (int v)
      {
         return v * v;
      }
   };

   class Child : public Parent
   {
      public: int doIt (int v, 
                        int av = 20)
      { return v * av;
      }
   };

   void main()
   {
      int i; 
      Parent *p = new Child();
      i = p->doIt(3);
   }

What is the value in i after execution of this program? One might expect 60, but it is 9 as the signature of doIt in Child does not match the signature in Parent. It therefore does not override the Parent doIt , merely overloads it, and the default is unusable.

Java also provides method overloading, where several methods can have the same name, but have different signatures.

The Eiffel philosophy is not to introduce a new technique, but to use genericity, inheritance and redefinition. Eiffel provides covariant signatures, which means the signatures of descendant routines do not have to match exactly, but they do have to conform, according to Eiffel's strong typing scheme.

Eiffel uses covariance with anchored types to implement examples such as max. The Vintage 95 Kernel Library specifies max as:

   max ( other : like Current): like Current

This says that the type of the argument to max must conform to the type of the current class. Therefore you get the same effect by redefinition without the overloading concept. You also get type checking to see that the parameter conforms to the current object. Genericity is also a mechanism that overcomes most of the need for overloading.

Further Reading (Ed.)

From Perl's Larry Wall Perl Culture: ..

Local ambiguity is okay

People thrive on ambiguity, as long as it is quickly resolved. Generally, within a natural language, ambiguity is resolved rapidly using recently spoken words and topics. Pronouns like "it" refer to things that are close by, syntactically speaking. Perl is full of little ambiguities that people never even notice because they're resolved so rapidly. For instance, many terms and operators in Perl begin with identical characters. Perl resolves them based on whether it's expecting to see a term or an operator, just as a person would. If you say 1 & 2, it knows that the & is a bitwise AND, but if you say &foo, it knows that you're calling subroutine foo.

In contrast, many strongly typed languages have "distant" ambiguity. C++ is one of the worst in this respect, because you can look at a + b and have no idea at all what the + is doing, let alone where it's defined. We send people to graduate school to learn to resolve distant ambiguities.

..

[ Eiffel Liberty (New) | GUERL | sOOap ] [ Top ]

Page is http://www.elj.com/eiffel/ij/fn-overloading/
Last Modified: 01 May 1998 (Created: 01 May 1998)