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