Topic: generalized overriding proposal


Author: bs@alice.att.com (Bjarne Stroustrup)
Date: 28 Oct 90 00:50:38 GMT
Raw View

This note is in response to a message sent to me by Kjell Eriksson
(erialfa!kjelle@data.nokia.se). He cannot be the only person
interested and I apologize for not posting a summary earlier:

 I got the C++ report for October today. It mentions
 a proposal for overriding. What? When? Why??

 Could you point me towards some reference or better
 still perhaps write a short article (very short) for
 comp.std.c++?

The issue is described under the title ``renaming'' on page 236 of the ARM:

******
 Merging two class hierarchies by using them as bases classes
 for a common derived class can cause a practical problem where
 the same name is used in both hierarchies, but where it refers
 to different operations in the different hierarchies.
 For example,

  class Lottery {
   // ...
   virtual int draw();
  };

  class GraphicalObject {
   // ...
   virtual void draw();
  };

  class LotterySimulation
  : public Lottery ,
  public GraphicalObject {
   // ...
  };

 In LotterySimulation we would like to override both Lottery::draw()
 and GraphicalObject::draw(), but with two distinct functions,
 since draw() has completely different meanings in the two base
 classes. We would also like LotterySimulation to have distinct,
 unambiguous names for the inherited functions Lottery::draw() and
 GraphicalObject::draw(). The semantics of this concept are simple,
 and the implementation is trivial; the problem seems to be to find
 a suitable syntax. The following has been suggested:

  class LotterySimulation
  : public Lottery ,
  public GraphicalObject {
   // ...
   virtual int l_draw() = Lottery::draw;
   virtual void go_draw() = GraphicalObject::draw;
  };

 This would extend the pure virtual syntax in a natural manner.
 It could be considered a weakness that the notation applies only
 to virtual functions. Alternatively, this can be considered the
 real beauty of the scheme.

******

As you see the basic solution is also suggested there. In the extensions
working group of X3J16 (The ANSI C++ standards group) we have waded through
many details and alternative schemes that I'd prefer not to go into here
(I'm short on time as ever). Maybe, if this summary is considered too brief,
other members of the working group can come to my rescue.

One question seems key to the solutions: Should a name that has been overridden
by another name still be visible? We more or less concluded that the answer
must be YES. This is irrelevant to the example above since a simple reference
to draw() for Lotterysimulation would be ambiguous anyway, but it is relevant
for names overriden in a single inheritance hierarchy and for overloaded names.

I - and apparently most C++ programmers - considered the overriding problem
unsolvable in C++ as defined. Since the problem is real and potentially nasty
this would imply that some language extension - like the one suggested in the
ARM - is necessary. However the problem do have a solution:

 class L2: public Lottery {
  virtual int l_draw() = 0;
  int draw() { return l_draw(); } // overrides Lottery::draw
 };

 class G2 : public GraphicalObject {
  virtual void go_draw() = 0;
  void draw() { go_draw(); } // overrides GraphicalObject::draw
 };

 class LotterySimulation : public L2 , public G2 {
  // ...
  int l_draw();
  void go_draw();
 };

That is, we use a forwarding function for each function we need a
new name for. Overriding the original function with a function that
simply calls the new function ensures that an LotterySimulation can
be treated as, say, a Lottery and a call of draw() will correctly call
an l_draw() defined by a LotterySimulation or a class derived from
LotterySimulation.

This technique is so simple - and in retrospect so obvious - that it
must have been independently invented many times but in this context
the credit goes to Doug McIlroy.

This implies that we must argue for a language extension based on elegance,
efficiency, or convenience; NOT on necessity.

I currently have a weak preference for making no change. This preference is
based on the assumption that such renaming of a function is not something a
programmer will need to do every week.

As all interesting techniques generalized overriding has uses beyond solving
the problem it was invented to solve. Part of the big question is to which
extent these ``additional'' uses makes the language complexity implied by
generalizing overriding worth while. In other words: Are we seeing an
example of creeping featurism or an example of useful language evolution?

Here are two examples of uses in addition to ``renaming:''

Name Merging:

 Consider

  class B1 {
  public:
   virtual int print();
  };

  class B2 {
  public:
   virtual int output();
  };

 Here we have two different named functions intended to perform
 the same service. To avoid having to maintain two separate names
 for this output operation in a class derived from both B1 and
 B2 we need a mechanism to say that a function overrides both:

  class D : public B1, public B2 {
  public:
   virtual int draw() = { B1::print, B2::output };
  };

Static Overriding Checking:

 Note that given

  class B {
   // ...
   virtual int f();
  };

 the degenerate form of generalized overnaming where f is explicitly
 overridden by f

  class D : public B {
  public:
   virtual int f() = B::f;
  };

 is legal and is identical to the ``plain'' version

  class D : public B {
  public:
   int f();
  };

 wherever there really is a B::f of the right type to override.

 If the name of the function being overridden does not refer to
 a virtual function of the exact type of the overriding function
 a compiler time error results. For example:

  class DD : public B {
  public:

   virtual int f(int) = B::f; // error: there is no B::f(int)
  };