Topic: Puzzler: simple single inheritance; should be easy, right?


Author: 95als@williams.edu (Adam Seligman)
Date: 6 Dec 1994 22:32:15 -0500
Raw View
Here's a bit a code I have gotten surprising results with on different
compilers.  Any ARM pointers that will clarify?  See if you resolve
the method binding in your head...you've got a 50% chance.

Adam
95als@williams.edu

// c++ source follows

#include <iostream.h>

class B {
public:
  void virtual f(void) { cout << "B::f(void) called" << endl; }
};

class D1: public B {
public:
  void f(int) { cout << "D1::f(int) called" << endl; }
};

class D2: public D1 {
public:
  void f(void) { cout << "D2::f(void) called" << endl; }
};

class D3: public D2 {
public:
  void f(void) { cout << "D3::f(void) called" << endl; }
};

main () {
  D3 d3;
  D2 *d2p = &d3;

  d2p->f(); //Who do I call?
}




Author: jason@cygnus.com (Jason Merrill)
Date: Wed, 7 Dec 1994 08:19:16 GMT
Raw View
Interesting.  The issue, of course, is whether or not D2::f(void) is a
virtual function.  The relevant passage is

  10.3  Virtual functions                                [class.virtual]

2 If a virtual member function vf is declared in a class Base and  in  a
  class  Derived,  derived  directly  or  indirectly from Base, a member
  function vf with the same name and same parameter list as Base::vf  is
  declared,  then  Derived::vf  is also virtual (whether or not it is so
  declared)  and  it  overrides1) Base::vf.

This doesn't say anything about intervening declarations which hide
Base::vf, so I must conclude that D2::f(void) is indeed virtual, and the
program should call D3::f(void).

Jason




Author: 95als@williams.edu (Adam Seligman)
Date: 7 Dec 1994 16:03:30 -0500
Raw View
In response to the source code:
#include <iostream.h>
class B {
public:
  void virtual f(void) { cout << "B::f(void) called" << endl; }
};

class D1: public B {
public:
  void f(int) { cout << "D1::f(int) called" << endl; }
};

class D2: public D1 {
public:
  void f(void) { cout << "D2::f(void) called" << endl; }
};

class D3: public D2 {
public:
  void f(void) { cout << "D3::f(void) called" << endl; }
};

main () {
  D3 d3;
  D2 *d2p = &d3;

  d2p->f();
}


jason@cygnus.com wrote:
> Interesting.  The issue, of course, is whether or not D2::f(void) is a
> virtual function.  The relevant passage is
>
>  10.3  Virtual functions                                [class.virtual]
>
> 2 If a virtual member function vf is declared in a class Base and  in  a
> class  Derived,  derived  directly  or  indirectly from Base, a member
> function vf with the same name and same parameter list as Base::vf  is
> declared,  then  Derived::vf  is also virtual (whether or not it is so
> declared)  and  it  overrides1) Base::vf.
>
> This doesn't say anything about intervening declarations which hide
> Base::vf, so I must conclude that D2::f(void) is indeed virtual, and the
> program should call D3::f(void).

Just so everyone is aware, Jason quoted the working paper, not the
ARM.  I agree with him that the working paper makes it seem plausible
that once an argument-signature has been used as virtual, any method
in a derived class with a matching argument-signature will be virtual.
I am using "argument-signature" to mean the name of a method and its
argument types to form a signature.

However, the ARM is ambiguous:

10.2 Virtual Functions
  If a class "base" contains a virtual function vf, and a class
  "derived" derived from it also contains a function vf of the same
  type, then a call of vf for an object of class "derived" invokes
  derived::vf (even if the access is through a pointer or reference to
  base).  The derived class function is said to override the base class
  function.  If the function types are different, however, the functions
  are considered different and the virtual function mechanism is not
  invoked.

This sounds like the decision of virtuality is made on the basis of the
derived class' direct base class.  In light of this, it seems that
the revision in the working paper, that you quoted above, about
"derived directly or indirectly from Base," meant to address the issue
of a method name not being used in an intermediate class:

class B {
public:
  virtual vf(void) { }
};

class D1: public B {
public:
  //... but no vf(anything)
};

class D2: public D1 {
public:
  vf(void) { cout << "D2::vf(void) called" << endl; }
};

// D2::vf(void) should be virtual
//  let's check by adding another class...

class D3: public D2 {
public:
  vf(void) { cout << "D3::vf(void) called" << endl; }
};

main() {
  D3 d3;
  D2 *d2p = &d3;
  d2p->vf();
}

Which in turns shows, that D2::vf(void) is of course virtual.  The ARM
doesn't make that clear, and I think the working paper revision was
meant to address that.  I have been told that different compilers are
giving different responses.

If a method is hidden, it doesn't ever appear in children of the class
it was hidden in, unless introduced again (defined or inherited from
somewhere else).  So hiding seems to be pretty solid.  So why does the
virtuality of an arugment-signature sneak by it?  I understand that an
indirect call of the method, through a pointer to base class in which
the method is virtual, will always go look it up in the child class,
and this is a different issue...

Any thoughts?

Adam
95als@williasm.edu











Author: jason@cygnus.com (Jason Merrill)
Date: Thu, 8 Dec 1994 01:23:25 GMT
Raw View
>>>>> Adam Seligman <95als@williams.edu> writes:

> If a method is hidden, it doesn't ever appear in children of the class
> it was hidden in, unless introduced again (defined or inherited from
> somewhere else).  So hiding seems to be pretty solid.  So why does the
> virtuality of an arugment-signature sneak by it?

It's hidden, but it's not gone.  It can still be used if you cast up above
the hiding declaration.

Here's a contrived example of why this behavior (propagating 'virtual')
might be useful:

struct A {
  virtual void f ();
};

struct B: public virtual A {
  void f (int);
};

struct C: public B, public virtual A {
  void f ();
};

In this hierarchy, does C::f override A::f?  The hiding rules tell us that
B::f hides A::f even though there is a path to A that does not pass
through B.  It would be confusing for a function to fail to override a
virtual function in a direct base class.

In general, hiding virtual functions should be considered poor programming
practice; why break polymorphism?

Jason




Author: kuhlins@hawk.wifo.uni-mannheim.de (Stefan Kuhlins)
Date: 08 Dec 1994 08:09:36 GMT
Raw View
In article <3c3abvINN74q@bigbird.cc.williams.edu> 95als@williams.edu (Adam Seligman) writes:

   class B {
   public:
     void virtual f(void) { cout << "B::f(void) called" << endl; }
   };

   class D1: public B {
   public:
     void f(int) { cout << "D1::f(int) called" << endl; }
   };


This is a bad design, because D1::f hides the virtual function B::f.

- Stefan





Author: 95als@williams.edu (Adam Seligman)
Date: 8 Dec 1994 10:14:35 -0500
Raw View
kuhlins@hawk.wifo.uni-mannheim.de (Stefan Kuhlins) wrote:

In article <3c3abvINN74q@bigbird.cc.williams.edu> 95als@williams.edu
(Adam Seligman) writes:
>   class B {
>   public:
>     void virtual f(void) { cout << "B::f(void) called" << endl; }
>   };
>
>   class D1: public B {
>   public:
>     void f(int) { cout << "D1::f(int) called" << endl; }
>   };
>
>
> This is a bad design, because D1::f hides the virtual function B::f.
>
> - Stefan

I would certainly agree that the hiding of a virtual function is a
dubious choice.  However, this example elucidates a decision made by
compiler writers in their intrepretation of an ambiguous C++
standard.  Although persuasive arguments can be made one way or
another about the ugliness of such code, it is legal C++.  More
importantly, I am to this point convinced that the relevant sections
of the ARM (and working paper, for that matter) are ambiguous on the
method invoked with this code.  Is virtuality, once established for an
argument-signature, permanent for that arguemnt signature in any
derived class, or will an intervening "hiding" of that argument-signature
free it to be static again?

I am eager to hear any ARM or working paper references that would help
clarify what I perceive to be an ambiguity in the standard.  I am not
coding a program, I'm writing a paper.

Adam
95als@williams.edu