[comp.lang.c++] When polymorphic, when not?

sdm@cs.brown.edu (Scott Meyers) (12/08/89)

I'm having trouble figuring out under which conditions virtual functions
are invoked polymorphically.  Consider the following program, which has
results shown for AT&T CC 2.0 and (where possible) g++ 1.36.2:

     1	#include <stream.h> 
     2	
     3	struct B { virtual void foo() { cout << "B::foo\n"; }};
     4	struct D: public B { void foo() { cout << "D::foo\n"; }};
     5	
     6	void val(B p) { p.foo(); }
     7	void ref(B &p) { p.foo(); }
     8	
     9	main() {
    10	  {
    11	    D d;
    12	    B b;
    13	    
    14	    b = d;
    15	    D &dr = d;
    16	    B &br = d;
    17	    
    18	    val(b);       // calls B::foo
    19	    val(br);      // calls D::foo [g++ calls B::foo]
    20	    ref(b);       // calls B::foo [g++ calls D::foo]
    21	    ref(br);      // calls D::foo
    22	    
    23	    val(d);       // calls D::foo [g++ calls B::foo]
    24	    val(dr);      // calls D::foo [g++ calls B::foo]
    25	    ref(d);       // calls D::foo 
    26	    ref(dr);      // calls D::foo
    27	  }
    28	
    29	  // I don't know what results g++ gives for the following section
    30	  {
    31	    D d;
    32	    B b = d;     // g++ won't compile this line ("unexpected argument
    33	                 // to constructor `B'")
    34	
    35	    D &dr = d;
    36	    B &br = d;
    37	    
    38	    val(b);       // calls D::foo
    39	    val(br);      // calls D::foo
    40	    ref(b);       // calls D::foo
    41	    ref(br);      // calls D::foo
    42	    
    43	    val(d);       // calls D::foo
    44	    val(dr);      // calls D::foo
    45	    ref(d);       // calls D::foo 
    46	    ref(dr);      // calls D::foo
    47	  }
    48	}

Why are the results of lines 18 and 20 different from lines 38 and 40?

Who is right for lines 19, 20, 23, 24 -- CC or g++?

How is it possible for function val, which takes a B as an argument (not a
pointer to a B or a reference to a B) to ever call anything other than
B::foo? 

Does either of the compilers exhibit any bugs in this example?

Is there a simple set of consistent rules that will allow me to predict
what's going to happen in cases like this?  That is, if I have a function f
with a formal parameter p and I pass object o to f as the actual parameter,
can I predict which functions will be invoked on p given:
    - The type of o (e.g., simple object, reference to an object, or pointer
      to an object)
    - The type of p (e.g., simple object, reference, or pointer)


Scott
sdm@cs.brown.edu

zweig@brutus.cs.uiuc.edu (Johnny Zweig) (12/08/89)

I think C-front's behavior in this case is wrong.  The function val() takes
an actual B as an argument (not a pointer to a thing of type B) --  so it
ought to get a memberwise copy of the B members of its argument, and should
not have the subclass-of-B virtual function pointer (consider the case in
which the subclass virtual function manipulates members that are not even
members of a B; those didn't even get passed in to val()!).

It seems the rule ought to be "if it takes a Foo, it takes a real-live
Foo (including doing the Foo version of virtual functions.  If it takes
a pointer/reference to a Foo, it will be able to find all the stuff it
needs to do the Polymorphic Thang."

Of course, since Cfront is the specification of the language, this might
be an incorrect argument.

-Johnny

ark@alice.UUCP (Andrew Koenig) (12/08/89)

In article <22395@brunix.UUCP>, sdm@cs.brown.edu (Scott Meyers) writes:

      1	#include <stream.h> 
      2	
      3	struct B { virtual void foo() { cout << "B::foo\n"; }};
      4	struct D: public B { void foo() { cout << "D::foo\n"; }};
      5	
      6	void val(B p) { p.foo(); }
      7	void ref(B &p) { p.foo(); }
      8	
      9	main() {
     10	  {
     11	    D d;
     12	    B b;
     13	    
     14	    b = d;
		// b is still a B

     15	    D &dr = d;
     16	    B &br = d;
     17	    
     18	    val(b);       // calls B::foo
     19	    val(br);      // calls D::foo [g++ calls B::foo]
		// g++ is correct here.  This is a bug in cfront 2.0,
		// which will be fixed in 2.1

     20	    ref(b);       // calls B::foo [g++ calls D::foo]
		// cfront is correct here.  b is a B.

     21	    ref(br);      // calls D::foo
		// correct.  br refers to a D.
     22	    
     23	    val(d);       // calls D::foo [g++ calls B::foo]
     24	    val(dr);      // calls D::foo [g++ calls B::foo]
		// these two lines exhibit the same cfront bug as above.

     25	    ref(d);       // calls D::foo 
     26	    ref(dr);      // calls D::foo
		// correct.

     27	  }
     28	
     29	  // I don't know what results g++ gives for the following section
     30	  {
     31	    D d;
     32	    B b = d;     // g++ won't compile this line ("unexpected argument
     33	                 // to constructor `B'")
		// apparently a bug in g++.  B always has a copy
		// constructor B::const(B&) and you can bind the
		// argument of that constructor to a D.
     34	
     35	    D &dr = d;
     36	    B &br = d;
     37	    
     38	    val(b);       // calls D::foo
     39	    val(br);      // calls D::foo
     40	    ref(b);       // calls D::foo
     41	    ref(br);      // calls D::foo
     42	    
     43	    val(d);       // calls D::foo
     44	    val(dr);      // calls D::foo
     45	    ref(d);       // calls D::foo 
     46	    ref(dr);      // calls D::foo
     47	  }
     48	}
-- 
				--Andrew Koenig
				  ark@europa.att.com