sdm@cs.brown.edu (Scott Meyers) (10/30/90)
Normally, C++ doesn't complain about potential ambituity, only ambiguity at a point of call. With MI, for example, if a function is inherited through two paths, the function to be called must normally be disambiguated only when it is called. As the ARM puts it (p. 206): Only the *use* of a name in an ambiguous way is an error, however; combining classes that contain members with the same name in an inheritance scheme is not an error. But consider the following example, where function foo is inherited through two paths in class Bottom: class VBase { public: virtual void foo() = 0; }; class Middle1: virtual public VBase { public: virtual void foo(); }; class Middle2: virtual public VBase { public: virtual void foo(); }; class Bottom: public Middle1, public Middle2 { }; AT&T cfront 2.0 and Sun's cfront 2.1 (beta) both give the following error: "ambiguity.C", line 16: error: virtual base: ambiguous Middle1::foo() and Middle2::foo() On the other hand, g++ 1.37 takes it without complaining. Furthermore, if either Middle1 or Middle2 is made a nonvirtual base class, then cfront is happy, too, even though the ambiguity remains. 1. Does anybody feel this is not an error in cfront? 2. Can anybody explain to me why it should make a difference whether a base class is virtual or not in the above example? Thanks, Scott sdm@cs.brown.edu
shopiro@alice.att.com (Jonathan Shopiro) (10/31/90)
In article <54733@brunix.UUCP>, sdm@cs.brown.edu (Scott Meyers) writes: > Normally, C++ doesn't complain about potential ambituity, only ambiguity at > a point of call. With MI, for example, if a function is inherited through > two paths, the function to be called must normally be disambiguated only > when it is called. As the ARM puts it (p. 206): > > Only the *use* of a name in an ambiguous way is an error, however; > combining classes that contain members with the same name in an > inheritance scheme is not an error. > > But consider the following example, where function foo is inherited through > two paths in class Bottom: > > class VBase { > public: > virtual void foo() = 0; > }; > > class Middle1: virtual public VBase { > public: > virtual void foo(); > }; > > class Middle2: virtual public VBase { > public: > virtual void foo(); > }; > > class Bottom: public Middle1, public Middle2 { > }; > > AT&T cfront 2.0 and Sun's cfront 2.1 (beta) both give the following error: > > "ambiguity.C", line 16: error: virtual base: ambiguous Middle1::foo() > and Middle2::foo() The problem here is that the vtbl can't be constructed. Suppose you had the code VBase* vbp = new Bottom; // pointer conversion vbp->foo(); // virtual function call Now the function call must be ambiguous, because you don't know whether to call Middle1::foo() or Middle2::foo(). But there is no way that the compiler can object to the call itself, because it can't know that vbp actually points to an instance of Bottom. So the error must be generated at the point of declaration. > On the other hand, g++ 1.37 takes it without complaining. But what function gets called? If it's either Middle1::foo() or Middle2::foo() I'd call that a bug in g++ 1.37. > Furthermore, if > either Middle1 or Middle2 is made a nonvirtual base class, then cfront is > happy, too, even though the ambiguity remains. No, actually the declarations are now okay, but the pointer conversion above becomes ambiguous, because there are now two instances of VBase hidden inside a Bottom object. You could write (note in the following at least one of Middle[12] is _not_virtually_derived_ from VBase): Bottom* bp = new Bottom; Middle1* m1p = new Bottom; Middle2* m2p = new Bottom; VBase* vbp; bp->foo(); // error: ambiguous call vbp = bp; // error: two instances of VBase vbp = m1p; vbp->foo(); // calls Middle1::foo() vbp = m2p; vbp->foo(); // calls Middle2::foo() > 1. Does anybody feel this is not an error in cfront? Apparently yes. > 2. Can anybody explain to me why it should make a difference whether a > base class is virtual or not in the above example? I hope the above helps. -- Jonathan E. Shopiro AT&T Bell Laboratories, Warren, NJ 07059-0908 shopiro@research.att.com (201) 580-4229
ilanc@microsoft.UUCP (Ilan CARON) (10/31/90)
In article <54733@brunix.UUCP> sdm@cs.brown.edu (Scott Meyers) writes: > class VBase { > public: > virtual void foo() = 0; > }; > > class Middle1: virtual public VBase { > public: > virtual void foo(); > }; > > class Middle2: virtual public VBase { > public: > virtual void foo(); > }; > > class Bottom: public Middle1, public Middle2 { > }; > > 1. Does anybody feel this is not an error in cfront? > > 2. Can anybody explain to me why it should make a difference whether a > base class is virtual or not in the above example? [Apologies if this is the nth response... our newsfeed is abysmally slow] This definition is in error since there is only *one* virtual function table slot in VBase's vft for function foo() -- hence when creating an instance of Bottom (which has only *one* embedded instance of VBase because it's derived from virtually), the compiler can't know which overriding function, Middle1::foo() or Middle2::foo(), to prefer. C++ has no rules for resolving this particular ambiguity (note that dominance is irrelevant here). (Hence instances of Bottom can't be created, hence the definition is in error). On the other hand, the non-virtual case is fine. There's no problem when creating an instance of Bottom: the vft of the Vbase part of Middle1 gets initialized with Middle1::foo(), similarly the Vbase part of Middle2. Of course, a *reference* to foo() through an instance of Bottom needs to be disambiguated: as in, a_bottom.Middle1::foo(). --ilan caron (uunet!microsoft!ilanc) P.S. Yeah, I just *love* virtual inheritance...
jgro@lia (Jeremy Grodberg) (10/31/90)
In article <54733@brunix.UUCP< sdm@cs.brown.edu (Scott Meyers) writes:
<Normally, C++ doesn't complain about potential ambituity, only ambiguity at
<a point of call. [...] But consider the following example, where function
<foo is inherited through two paths in class Bottom:
<
< class VBase {
< public:
< virtual void foo() = 0;
< };
<
< class Middle1: virtual public VBase {
< public:
< virtual void foo();
< };
<
< class Middle2: virtual public VBase {
< public:
< virtual void foo();
< };
<
< class Bottom: public Middle1, public Middle2 {
< };
<
<AT&T cfront 2.0 and Sun's cfront 2.1 (beta) both give the following error:
<
< "ambiguity.C", line 16: error: virtual base: ambiguous Middle1::foo()
< and Middle2::foo()
<
<On the other hand, g++ 1.37 takes it without complaining. Furthermore, if
<either Middle1 or Middle2 is made a nonvirtual base class, then cfront is
<happy, too, even though the ambiguity remains.
<
The ARM, on page 235, says "To avoid ambiguous function definitions, all
redefinitions of a virtual function from a virtual base class must occur
on a single path through the inheritance structure." This explains the
behavior of CFront. However, this quote is taken from the commentary on
how virtual base classes with virtual functions could be implemented,
not from any section which is part of the ANSI base document, so it has
no formal status as defining the language. The section that is part of
the formal specification does not address this issue at all (as far as I
can find).
In short, the reason CFront complains is because there is only one
pointer slot for a virtual function of a virtual base, and the compiler
can't put both Middle1::foo() and Middle2::foo() in the one slot.
g++ either uses a different mechanism for virtual function tables
(doubtful), fills the table's slot with a pointer to a function which
returns a run-time error message (reasonable), or just puts one of the two
functions in the table (a bug).
--
Jeremy Grodberg "I don't feel witty today. Don't bug me."
jgro@lia.com
sdm@cs.brown.edu (Scott Meyers) (10/31/90)
In article <54733@brunix.UUCP> I wrote: | class VBase { | public: | virtual void foo() = 0; | }; | | class Middle1: virtual public VBase { | public: | virtual void foo(); | }; | | class Middle2: virtual public VBase { | public: | virtual void foo(); | }; | | class Bottom: public Middle1, public Middle2 { | }; In article <58641@microsoft.UUCP> ilanc@microsoft.UUCP (Ilan CARON) writes: | This definition is in error since there is only *one* virtual function | table slot in VBase's vft for function foo() -- hence when creating an | instance of Bottom (which has only *one* embedded instance of VBase | because it's derived from virtually), the compiler can't know which | overriding function, Middle1::foo() or Middle2::foo(), to prefer. C++ | has no rules for resolving this particular ambiguity (note that | dominance is irrelevant here). (Hence instances of Bottom can't be created, | hence the definition is in error). What you say is true, but I claim this is a rationalization for a compiler error, not an explanation as to why the code itself is wrong. (Perhaps the code is wrong, but I'd like to see a reference to the ARM where it says it's wrong.) VBase::foo is pure virtual, which means it should never be called through the vtable; the ARM explicitly says the program behavior is undefined if that ever happens (p. 215). (In cfront 2.0 such a call would result in a runtime error.) So where foo's slot in VBase's vtable points makes no difference -- the pointer should never be used (which means it could be optimized out of existence). It also means that there can never be an object of type VBase, only objects of classes derived from VBase, and foo must be defined in those classes, so VBase::foo can't be called via a reference/pointer to a VBAse. When a Bottom object is created, the vtables for both Middle1 and Middle2 are used, and then there's no problem with space in the vtable. And of course the ambiguity on a call to foo still exists. So I'm unconvinced, I still think this is a compiler error. Am I still overlooking something? Scott sdm@cs.brown.edu
rfg@NCD.COM (Ron Guilmette) (10/31/90)
In article <54733@brunix.UUCP> sdm@cs.brown.edu (Scott Meyers) writes:
<Normally, C++ doesn't complain about potential ambituity, only ambiguity at
<a point of call. With MI, for example, if a function is inherited through
<two paths, the function to be called must normally be disambiguated only
<when it is called. As the ARM puts it (p. 206):
<
< Only the *use* of a name in an ambiguous way is an error, however;
< combining classes that contain members with the same name in an
< inheritance scheme is not an error.
I have always felt that this was a big mistake in the language design.
Why is it ever a good idea to let people build time bombs which just
sit around waiting to go off until somebody first touches them?
I feel that it would be much better to have compilers tell class authors
whenever they have created (unwittingly) the *possibility* of ambiguity.
Now somebody may argue that one might obtain a class A from one source of
code, and a class B from another source, and then one should be able to
write one's own class C, derived from both A and B, and have it pass
compilation in all cases (so that the author of C will not be bothered
unnecessarily with superfluous errors about inherited members of C
which never actually get used bu C's clients). In my opinion, such an
argument is just bunk. This is just a way of supporting and reinforcing
laziness on the part of the author of class C (who could easily fix all
conflicts by adding code within C and without touching either A or B).
<But consider the following example, where function foo is inherited through
<two paths in class Bottom:
[... text deleted...]
<AT&T cfront 2.0 and Sun's cfront 2.1 (beta) both give the following error:
<
< "ambiguity.C", line 16: error: virtual base: ambiguous Middle1::foo()
< and Middle2::foo()
<
<On the other hand, g++ 1.37 takes it without complaining. Furthermore, if
<either Middle1 or Middle2 is made a nonvirtual base class, then cfront is
<happy, too, even though the ambiguity remains.
<
< 1. Does anybody feel this is not an error in cfront?
Yes. At the very least, cfront should try to be more consistant about
what it defines as erroneous.
< 2. Can anybody explain to me why it should make a difference whether a
< base class is virtual or not in the above example?
I certainly can't. Perhaps the authors of cfront will step forward and
make a statement.
--
// Ron Guilmette - C++ Entomologist
// Internet: rfg@ncd.com uucp: ...uunet!lupine!rfg
// Motto: If it sticks, force it. If it breaks, it needed replacing anyway.
diamond@tkou02.enet.dec.com (diamond@tkovoa) (11/02/90)
In article <2351@lupine.NCD.COM> rfg@NCD.COM (Ron Guilmette) writes: <In article <54733@brunix.UUCP> sdm@cs.brown.edu (Scott Meyers) writes: <<With MI, for example, if a function is inherited through <<two paths, the function to be called must normally be disambiguated only <<when it is called. As the ARM puts it (p. 206): [deleted] < <I have always felt that this was a big mistake in the language design. <Why is it ever a good idea to let people build time bombs which just <sit around waiting to go off until somebody first touches them? People can build time bombs no matter what you do. Programmers who wish to be safe will disambiguate every use of an inherited component at the point where it is used, whether or not dismbiguation is necessary (yet). <I feel that it would be much better to have compilers tell class authors <whenever they have created (unwittingly) the *possibility* of ambiguity. A warning (switchable on or off) might be reasonable. This would be less helpful than you imagine because: <Now somebody may argue that one might obtain a class A from one source of <code, and a class B from another source, and then one should be able to <write one's own class C, derived from both A and B, and have it pass <compilation in all cases (so that the author of C will not be bothered <unnecessarily with superfluous errors about inherited members of C <which never actually get used bu C's clients). In my opinion, such an <argument is just bunk. This is just a way of supporting and reinforcing <laziness on the part of the author of class C (who could easily fix all <conflicts by adding code within C and without touching either A or B). Suppose your A vendor adds a new feature to A which you don't use, and your B vendor adds a new feature to B with the same name? Suppose B and C both come from the second vendor, C uses a feature of B and C uses other features of A (i.e. the second vendor is also a client of the first vendor), and the first vendor adds a feature to A with the same name? Do you wait for the second vendor to mail you a revised C? Or do you hope that they have already (previously unnecessarily) disambiguated all uses of inherited features, and that you yourself have done the same in your classes? -- Norman Diamond, Nihon DEC diamond@tkov50.enet.dec.com (tkou02 is scheduled for demolition) We steer like a sports car: I use opinions; the company uses the rack.
petergo@microsoft.UUCP (Peter GOLDE) (11/03/90)
>What you say is true, but I claim this is a rationalization for a compiler >error, not an explanation as to why the code itself is wrong. (Perhaps the >code is wrong, but I'd like to see a reference to the ARM where it says >it's wrong.) p. 235: To avoid ambiguous function definitions, all redefinitions of a virtual function from a virtual base must occur on a single path through the inheritance structure. --- Peter Golde petergo%microsoft@uunet.uu.net "Motion gives the illusion of progress"