Topic: Multiple inheritance ambiguity question
Author: harald@bion.kth.se (Matti Rendahl)
Date: 22 Apr 1994 16:29:13 GMT Raw View
In article <Co0pFr.1ru@ucc.su.OZ.AU> maxtal@physics.su.OZ.AU (John Max Skaller)
writes:
> struct Base { virtual void draw(); };
> struct Gun : virtual Base { void draw(); };
> struct Paint : virtual Base { void draw(); };
> struct GunPaint : Gun, Paint {};
> GunPaint gp;
> Base *b = &gp;
> b-> draw();
>
> Which draw() is called?
>
> I believe the committee has determined (or will deterimine?) that
> in fact the very derivation of GunPaint above is in error -- something
> I dont agree with. I think merely that GunPaint is like an abstract
> class and cant be instantiated until a derived class overrides void draw().
Is that true?? As far as I know, that trick is the only way of down-casting
from a virtual base class:
struct B { virtual void *me() { return this; }};
struct D1 : virtual B { void *me() { return this; }};
struct D2 : virtual B { void *me() { return this; }};
struct D3 : D1, D2 { void *me() { return this; }};
B *bp = new D3;
D3 *d3p = (D3 *) d3p->me();
So this will be forbidden because of the potential ambiguity between
D1::me and D2::me?
I'm aware that there will be better ways of doing down-casts in the
future, but restrictions tend to get implemented a lot sooner than
extensions. Also, such a restriction will surly break a lot of existing
code.
-----------------------------------------------------------------------------
Harald Winroth | Computational Vision and Active Perception Laboratory,
harald@bion.kth.se | Royal Institute of Technology, S-10044 Stockholm, Sweden
-----------------------------------------------------------------------------
Author: maxtal@physics.su.OZ.AU (John Max Skaller)
Date: Sun, 24 Apr 1994 22:58:44 GMT Raw View
In article <HARALD.94Apr22182913@moon.bion.kth.se> harald@bion.kth.se (Matti Rendahl) writes:
>In article <Co0pFr.1ru@ucc.su.OZ.AU> maxtal@physics.su.OZ.AU (John Max Skaller)
>
>Is that true?? As far as I know, that trick is the only way of down-casting
>from a virtual base class:
>
> struct B { virtual void *me() { return this; }};
> struct D1 : virtual B { void *me() { return this; }};
> struct D2 : virtual B { void *me() { return this; }};
> struct D3 : D1, D2 { void *me() { return this; }};
>
> B *bp = new D3;
> D3 *d3p = (D3 *) d3p->me();
You mean (D3*) "bp->me()".
>
>So this will be forbidden because of the potential ambiguity between
>D1::me and D2::me?
There's nothing wrong with this code because D3::me()
overrides both D1::me and D2::me. The problem would arise if
D3::me was not declared:
struct D3 : D1, D2 { /* void *me() { return this; } */ };
so that
bp->me();
would have to be an error of some kind : either D3 declaration
is in error, or instantiation by 'new D3' is in error (I prefer),
or the call bp->me() causes an exception or undefined behaviour.
If you _also_ remove the declaration from B:
struct B { /* virtual void *me() { return this; } */};
the situtation is different. No call bp->me() is possible.
Calls through pointers to D1 and D2 are fine. Calls through
D3 pointers are static errors detected at compile time
as an ordinary ambiguity.
--
JOHN (MAX) SKALLER, INTERNET:maxtal@suphys.physics.su.oz.au
Maxtal Pty Ltd, CSERVE:10236.1703
6 MacKay St ASHFIELD, Mem: SA IT/9/22,SC22/WG21
NSW 2131, AUSTRALIA
Author: fjh@munta.cs.mu.OZ.AU (Fergus Henderson)
Date: Thu, 28 Apr 1994 20:04:44 GMT Raw View
harald@bion.kth.se (Matti Rendahl) writes:
>maxtal@physics.su.OZ.AU (John Max Skaller) writes:
>
>> struct Base { virtual void draw(); };
>> struct Gun : virtual Base { void draw(); };
>> struct Paint : virtual Base { void draw(); };
>> struct GunPaint : Gun, Paint {};
>> GunPaint gp;
>> Base *b = &gp;
>> b-> draw();
>>
>> Which draw() is called?
>>
>> I believe the committee has determined (or will deterimine?) that
>> in fact the very derivation of GunPaint above is in error
>
>Is that true?? As far as I know, that trick is the only way of down-casting
>from a virtual base class:
>
> struct B { virtual void *me() { return this; }};
> struct D1 : virtual B { void *me() { return this; }};
> struct D2 : virtual B { void *me() { return this; }};
> struct D3 : D1, D2 { void *me() { return this; }};
>
> B *bp = new D3;
> D3 *d3p = (D3 *) d3p->me();
>
>So this will be forbidden because of the potential ambiguity between
>D1::me and D2::me?
No, because here you have explicitly overriden me() in D3, so there
is no ambiguity - D3::me will be called. This is a different situation
to John Skaller's example.
--
Fergus Henderson - fjh@munta.cs.mu.oz.au
Author: jamshid@ses.com (Jamshid Afshar)
Date: 1 Apr 1994 16:51:04 -0600 Raw View
In article <KANZE.94Mar24152618@slsvhdt.us-es.sel.de>,
James Kanze <kanze@us-es.sel.de> wrote:
>In article <9403171945.AA16441@ses.com> jamshid@ses.com (Jamshid
>Afshar) writes:
struct GunFighter {
virtual void draw() { cout << "Bang."; /*...*/; }
void get_mad() { draw(); }
};
struct Image {
virtual void draw() { cout << "Pretty"; /*...*/; }
};
class Cartoon : public Image, public GunFighter {};
main() {
Cartoon c;
c.get_mad();
// c.draw(); -- error: ambiguous
}
>|> [...] The above program should execute GunFighter::draw().
>|> You should definitely report a compiler bug if it doesn't. g++ 2.5.5
>|> and Cfront 3.0.2 do the right thing.
>
>Why should it prefer GunFighter::draw to Image::draw? The fact that
>the functions are virtual here should not really make a difference,
>since there is no pointer or reference involved. The compiler *knows*
>the full type of the object in question. Any decent compiler should
>give an error.
I think you're missing something. When I wrote that the above program
should execute GunFighter::draw(), I meant that c.get_mad() is called
in main() and it executes GunFighter::draw(). Do you disagree or were
you confused by the mentioning of the unrelated ambiguity error?
>The problem occurs if I have a reference or a pointer to a Cartoon.
>Should the compiler refuse to generate the call because the function
>is ambiguous. A class derived from Cartoon may have overridden
>'draw', and it would not be ambiguous. This is, I think, the
>question. Is such a program illegal (requiring a compiler
>diagnostic), or simply undefined (and may be core dumping on
>execution).
If you have a pointer/reference or an object whose static type is
Cartoon, you cannot call draw() because it is ambiguous. If you use a
pointer/reference to either base class, you can call draw() and it
chooses the draw() that is in that scope.
Cartoon sam;
sam.draw(); // error: ambiguous
GunFighter& gf = sam;
gf.draw(); // okay: "bang"
>|> Note, if you happened to define a Cartoon::draw() function, it would
>|> override *both* GunFighter::draw() and Image::draw(). I think ARM
>|> 10.1.1 and 10.2 discuss this.
>
>If I understand the intent of section 10.2, calling 'draw' through a
>pointer to GunFighter should get GunFighter::draw, and calling through
>a pointer to Image should get Image::draw. But it is not 100% clear,
>since there is much reference to `derived::f', which in this case maps
>to Cartoon::draw. Cartoon::draw is unambiguously ambiguous.
I can't point to anything that explicitly says GunFighter::get_mad()
would call GunFighter::draw() (as opposed to being undefined), but the
ARM never implies that unrelated classes interfere with each others'
virtual functions.
10.2: 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 [...] the derived class function is said to
override the base class function.
The January WP is a bit clearer -- it uses the word "declares" instead
of "contains".
I'm not sure what you mean by your last statement that Cartoon::draw
is ambiguous. If you mean that calling draw() on a Cartoon
object/reference/pointer is an ambiguity error, then I agree. If you
mean that declaring a function draw() in Cartoon is ambiguous, then
no, I disagree. There's an example of this in 10.8c (commentary).
It's basically says:
struct A { virtual void f(); };
struct B { virtual void f(); };
struct C : public A, public B { void f(); };
C* pc = new C;
B* pb = pc;
A* pa = pa;
// all the following invoke C::f()
pa->f();
pb->f();
pc->f();
Jamshid Afshar
jamshid@ses.com
Author: kanze@us-es.sel.de (James Kanze)
Date: 05 Apr 1994 18:16:27 GMT Raw View
In article <9404012231.AA16727@ses.com> jamshid@ses.com (Jamshid
Afshar) writes:
|> In article <KANZE.94Mar24152618@slsvhdt.us-es.sel.de>,
|> James Kanze <kanze@us-es.sel.de> wrote:
|> >In article <9403171945.AA16441@ses.com> jamshid@ses.com (Jamshid
|> >Afshar) writes:
|> struct GunFighter {
|> virtual void draw() { cout << "Bang."; /*...*/; }
|> void get_mad() { draw(); }
|> };
|> struct Image {
|> virtual void draw() { cout << "Pretty"; /*...*/; }
|> };
|> class Cartoon : public Image, public GunFighter {};
|> main() {
|> Cartoon c;
|> c.get_mad();
|> // c.draw(); -- error: ambiguous
|> }
|> >|> [...] The above program should execute GunFighter::draw().
|> >|> You should definitely report a compiler bug if it doesn't. g++ 2.5.5
|> >|> and Cfront 3.0.2 do the right thing.
|> >
|> >Why should it prefer GunFighter::draw to Image::draw? The fact that
|> >the functions are virtual here should not really make a difference,
|> >since there is no pointer or reference involved. The compiler *knows*
|> >the full type of the object in question. Any decent compiler should
|> >give an error.
|> I think you're missing something. When I wrote that the above program
|> should execute GunFighter::draw(), I meant that c.get_mad() is called
|> in main() and it executes GunFighter::draw(). Do you disagree or were
|> you confused by the mentioning of the unrelated ambiguity error?
I was a little confused when I first responded, and then didn't read
your example as carefully as I should have. Basically, I jumped on
one sentence in the ARM, out of context, and misinterpreted it. (The
context makes it clear what was meant.)
It is now quite clear to me that a function in a base class can never
become ambiguous through derivation when called through the base class
pointer. I simply saw a reference to derived::f() (which is
ambiguous) in the ARM, and didn't read any farther. The sentence in
question, however, contains a subordinate clause which limits
consideration to the cases where the derived class actually has an
'f()' of its own. As you point out later, the working papers
clarifies this even more, by expressedly using the word `declared',
but I don't really think that the ARM was ambiguous here. Just sloppy
reading on my part.
--
James Kanze email: kanze@lts.sel.alcatel.de
GABI Software, Sarl., 8 rue du Faisan, F-67000 Strasbourg, France
Conseils en informatique industrielle --
-- Beratung in industrieller Datenverarbeitung
Author: maxtal@physics.su.OZ.AU (John Max Skaller)
Date: Sun, 10 Apr 1994 00:31:03 GMT Raw View
In article <KANZE.94Apr5191627@slsvhdt.us-es.sel.de> kanze@us-es.sel.de (James Kanze) writes:
>It is now quite clear to me that a function in a base class can never
>become ambiguous through derivation when called through the base class
>pointer.
Wrong.
struct Base { virtual void draw(); };
struct Gun : virtual Base { void draw(); };
struct Paint : virtual Base { void draw(); };
struct GunPaint : Gun, Paint {};
GunPaint gp;
Base *b = &gp;
b->draw();
Which draw() is called?
I believe the committee has determined (or will deterimine?) that
in fact the very derivation of GunPaint above is in error -- something
I dont agree with. I think merely that GunPaint is like an abstract
class and cant be instantiated until a derived class overrides void draw().
--
JOHN (MAX) SKALLER, INTERNET:maxtal@suphys.physics.su.oz.au
Maxtal Pty Ltd, CSERVE:10236.1703
6 MacKay St ASHFIELD, Mem: SA IT/9/22,SC22/WG21
NSW 2131, AUSTRALIA
Author: kanze@us-es.sel.de (James Kanze)
Date: 15 Mar 1994 19:47:45 GMT Raw View
In article <1994Mar11.161406.4083@us-es.sel.de> howland@us-es.sel.de
(Gary Howland US/END 60/1/25) writes:
|> #include <iostream.h>
|> struct A
|> {
|> virtual void f() { cout << "A::f" << endl; }
|> void g() { f(); }
|> };
|> struct B
|> {
|> virtual void f() { cout << "B::f" << endl; }
|> };
|> class C : public B, public A
|> {
|> };
|> main()
|> {
|> C c;
|> c.g();
|> }
|> When I compile this I get the (expected) warnings regarding an ambiguity
|> between A::f() & B::f(). However, the program also dumps core when run.
|> It dumps core in A::g(). The reason is that all the vtab function pointers
|> for the object c are null.
|> I can understand the vtab for the "C-part" of the object c being undefined,
|> due to the ambiguity, but I am surprised that the vtabs for the A & B parts
|> of this object are undefined.
There are no vtab's for the C-part of object c. From a standards
point of view, there are no vtab's, period. (This is an
implementation technique.)
By language definition, once an object is constructed, calling a
virtual function should always get the function in the most derived
class. This may be a function actually declared in the most derived
class, or the function actually called may be in the most derived
class by inheritance. But it *must* be the function of the most
derived class.
In the above case, the function called must be C::f. But C::f is
ambiguous. So the program is illegal.
|> So, my question is, is this behaviour valid C++?
Good question. The above program is illegal, but I don't know off
hand if a compiler is required to diagnose it (which would practically
mean that you cannot instantiate a C) or not. From a practical point
of view, it is not technically feasable for a compiler to diagnose
every occurance; what happens if you call g() in the constructor of C,
for example.
As a quality of implementation issue, on the other hand, a core dump
is not very good. A run-time error message to stderr and an abort
would seem a better solution.
--
James Kanze email: kanze@lts.sel.alcatel.de
GABI Software, Sarl., 8 rue du Faisan, F-67000 Strasbourg, France
Conseils en informatique industrielle --
-- Beratung in industrieller Datenverarbeitung
Author: maxtal@physics.su.OZ.AU (John Max Skaller)
Date: Wed, 23 Mar 1994 17:00:00 GMT Raw View
In article <KANZE.94Mar17191153@slsvhdt.us-es.sel.de> kanze@us-es.sel.de (James Kanze) writes:
>In article <1994Mar16.112926.15921@us-es.sel.de> howland@us-es.sel.de
>(Gary Howland US/END 60/1/25) writes:
>
> [Original example deleted...]
>
>|> > In the above case, the function called must be C::f. But C::f is
>|> > ambiguous. So the program is illegal.
>
>|> Hang on James, is this program really illegal, or is the behaviour
>|> just undefined?
>
>Do you mean, is the compiler obliged to issue a diagnostic. I don't
>think so. It is illegal in the same way that dereferencing a null
>pointer is illegal.
The current working paper has a conformance model
designed by Jonathan Shopiro in which the following terms are used:
ill-formed: language processor must issue a diagnostic
Example: syntax error.
undefined behaviour: the language processor
can do anything it wants, including core dump,
generate bad code, or delete your root directory :-)
No diagnostic is required.
Example: call pure virtual function in a constructor.
implementation-defined behaviour: the vendor is required
to specify what happens, and the language processor
has to do exactly that
Example: number of bytes in an "int"
unspecified behaviour: the vendor can pick from
a set of options given in the Standard
(Cant think of an example off hand :-)
There's no such thing as an "illegal" program. But the colloquialism
maps closest to "ill formed or undefined behaviour".
--
JOHN (MAX) SKALLER, INTERNET:maxtal@suphys.physics.su.oz.au
Maxtal Pty Ltd, CSERVE:10236.1703
6 MacKay St ASHFIELD, Mem: SA IT/9/22,SC22/WG21
NSW 2131, AUSTRALIA
Author: kanze@us-es.sel.de (James Kanze)
Date: 24 Mar 1994 18:45:01 GMT Raw View
In article <27812@alice.att.com> bs@alice.att.com (Bjarne Stroustrup)
writes:
|> > By language definition, once an object is constructed, calling a
|> > virtual function should always get the function in the most derived
|> > class. This may be a function actually declared in the most derived
|> > class, or the function actually called may be in the most derived
|> > class by inheritance. But it *must* be the function of the most
|> > derived class.
|> I think this explanation is misleading. The function called will be the
|> original one or an overriding one. I don't see the concept of ``most
|> derived class'' entering into the issue.
I think I actually got carried away, and read too much into a simple
expression in section 10.2 of the ARM. Basically, the sentance (the
first in the section) refers to "derived::vf". Taken alone,
"derived::vf" is ambiguous (in the example posted). However, earlier
in the sentence is a limiting clause (which I missed) restraining the
discussion to classes in which "derived" actually contains a function
of the same type (which it didn't in the example). While one could
argue that it contains such a function by inheritance, this would seem
to be stretching things greatly, and is not the most natural
interpretation of the sentence in question.
|> Maybe the overriding rule somehow got confused with the rule for
|> initializing virtual base classes?
|> > In the above case, the function called must be C::f. But C::f is
|> > ambiguous. So the program is illegal.
|> No. There is no C::f so C is illerrevant. A deriving classs C cannot
|> affect a base class A except through an explicit access or an overriding
|> and C does neither.
Yes. My confusion. The *expression* C::f is ambiguous. But this is
not the same thing.
--
James Kanze email: kanze@lts.sel.alcatel.de
GABI Software, Sarl., 8 rue du Faisan, F-67000 Strasbourg, France
Conseils en informatique industrielle --
-- Beratung in industrieller Datenverarbeitung
Author: howland@us-es.sel.de (Gary Howland US/END 60/1/25)
Date: Wed, 16 Mar 94 11:29:26 GMT Raw View
In article <KANZE.94Mar15204745@slsvhdt.us-es.sel.de>, kanze@us-es.sel.de (James Kanze) writes:
> In article <1994Mar11.161406.4083@us-es.sel.de> howland@us-es.sel.de
> (Gary Howland US/END 60/1/25) writes:
>
> |> #include <iostream.h>
>
> |> struct A
> |> {
> |> virtual void f() { cout << "A::f" << endl; }
> |> void g() { f(); }
> |> };
>
> |> struct B
> |> {
> |> virtual void f() { cout << "B::f" << endl; }
> |> };
>
> |> class C : public B, public A
> |> {
> |> };
>
> |> main()
> |> {
> |> C c;
> |> c.g();
> |> }
>
> |> When I compile this I get the (expected) warnings regarding an ambiguity
> |> between A::f() & B::f(). However, the program also dumps core when run.
>
> |> It dumps core in A::g(). The reason is that all the vtab function pointers
> |> for the object c are null.
>
> |> I can understand the vtab for the "C-part" of the object c being undefined,
> |> due to the ambiguity, but I am surprised that the vtabs for the A & B parts
> |> of this object are undefined.
>
>
> In the above case, the function called must be C::f. But C::f is
> ambiguous. So the program is illegal.
Hang on James, is this program really illegal, or is the behaviour just undefined?
> James Kanze email: kanze@lts.sel.alcatel.de
> GABI Software, Sarl., 8 rue du Faisan, F-67000 Strasbourg, France
> Conseils en informatique industrielle --
> -- Beratung in industrieller Datenverarbeitung
--
// Warning! Sometimes my return address is broken
Gary Howland Temporary : howland@lts.sel.alcatel.de
Internet CI$: 100021.3417@compuserve.com
CI$ : 100021,3417
Author: bs@alice.att.com (Bjarne Stroustrup)
Date: 17 Mar 94 17:56:08 GMT Raw View
kanze@us-es.sel.de (James Kanze) writes
> In article <1994Mar11.161406.4083@us-es.sel.de> howland@us-es.sel.de
> (Gary Howland US/END 60/1/25) writes:
>
> |> #include <iostream.h>
>
> |> struct A
> |> {
> |> virtual void f() { cout << "A::f" << endl; }
> |> void g() { f(); }
> |> };
>
> |> struct B
> |> {
> |> virtual void f() { cout << "B::f" << endl; }
> |> };
>
> |> class C : public B, public A
> |> {
> |> };
>
> |> main()
> |> {
> |> C c;
> |> c.g();
> |> }
>
> |> When I compile this I get the (expected) warnings regarding an ambiguity
> |> between A::f() & B::f(). However, the program also dumps core when run.
Why do you expect warnings? I see no problems with this example and for me it
compiles and runs without warnings or unexpected behavior.
A::g() calls A::f() and since A::f() hasn't been overridden the output produced
is
A::f
> |> It dumps core in A::g(). The reason is that all the vtab function pointers
> |> for the object c are null.
Curious.
> |> I can understand the vtab for the "C-part" of the object c being undefined,
> |> due to the ambiguity, but I am surprised that the vtabs for the A & B parts
> |> of this object are undefined.
I would be surprised also.
> There are no vtab's for the C-part of object c. From a standards
> point of view, there are no vtab's, period. (This is an
> implementation technique.)
Correct.
> By language definition, once an object is constructed, calling a
> virtual function should always get the function in the most derived
> class. This may be a function actually declared in the most derived
> class, or the function actually called may be in the most derived
> class by inheritance. But it *must* be the function of the most
> derived class.
I think this explanation is misleading. The function called will be the
original one or an overriding one. I don't see the concept of ``most
derived class'' entering into the issue.
Maybe the overriding rule somehow got confused with the rule for
initializing virtual base classes?
> In the above case, the function called must be C::f. But C::f is
> ambiguous. So the program is illegal.
No. There is no C::f so C is illerrevant. A deriving classs C cannot
affect a base class A except through an explicit access or an overriding
and C does neither.
> |> So, my question is, is this behaviour valid C++?
No. The program should compile, run, and produce ``A::f''.
- Bjarne
> Good question. The above program is illegal, but I don't know off
> hand if a compiler is required to diagnose it (which would practically
> mean that you cannot instantiate a C) or not. From a practical point
> of view, it is not technically feasable for a compiler to diagnose
> every occurance; what happens if you call g() in the constructor of C,
> for example.
> As a quality of implementation issue, on the other hand, a core dump
> is not very good. A run-time error message to stderr and an abort
> would seem a better solution.
Author: kanze@us-es.sel.de (James Kanze)
Date: 17 Mar 1994 18:11:53 GMT Raw View
In article <1994Mar16.112926.15921@us-es.sel.de> howland@us-es.sel.de
(Gary Howland US/END 60/1/25) writes:
[Original example deleted...]
|> > In the above case, the function called must be C::f. But C::f is
|> > ambiguous. So the program is illegal.
|> Hang on James, is this program really illegal, or is the behaviour
|> just undefined?
Do you mean, is the compiler obliged to issue a diagnostic. I don't
think so. It is illegal in the same way that dereferencing a null
pointer is illegal.
--
James Kanze email: kanze@lts.sel.alcatel.de
GABI Software, Sarl., 8 rue du Faisan, F-67000 Strasbourg, France
Conseils en informatique industrielle --
-- Beratung in industrieller Datenverarbeitung