Topic: Abstract Classes, pure virtuals, and "dominance


Author: erd@tigard.intel.com (Eric Dittert)
Date: 11 May 92 22:11:26 GMT
Raw View
In <1992May5.140406.17542@jyu.fi> sakkinen@jyu.fi (Markku Sakkinen) writes:

>In article <erd.704763095@tigard> erd@tigard.intel.com (Eric Dittert) writes:
>> ...
>>However, the stated purpose of this immediate-base-only-initialization-
>>rule (IBOIR) is to prevent multiple initializations by using a syntax
>>rule enforceable at compile time.
>>For single inheritance, and for multiple, non-virtual inheritance,
>>the IBOIR suffices to do this.  But for a multiply virtually inherited
>>class V, in general, there will be multiple derived class constructors
>>which will attempt to initialize V.  Any C++ compiler implementors who
>>are listening can set me straight if I am wrong, but it would seem to me
>>that a run time mechanism is necessary to prevent multiple initializations
>>in this case. The conclusion is that for a multiply virtually inherited
>>class, the IBOIR is not useful; IMHO, it simply should not apply in this
>>case.

>Without being a compiler implementor, it seems obvious to me that
>a compile-time mechanism is possible in principle, since the exact
>class of every object to be constructed is statically known.
>However, the mechanism would probably be quite complicated.

Hmm.  The reason that I thought that a compile-time mechanism is "impossible"
is that the various derived class constructors which will
attempt to initialize a virtual base class will, in general, be compiled
*separately*.  In other words, when the compiler is generating code
for a constructor for a class A which inherits virtually from a class V,
the compiler cannot know whether it is A which should initialize V
(by whatever rule you happen to favor) or not.  Since a single piece of
code (the constructor for A) cannot do two different things (call the
constructor for V or not) at different times without branching conditionally,
I concluded that a compile-time mechanism cannot suffice.

I now see my buried assumption and the accompanying alternative: you can
have a compile-time mechanism if you are willing to generate more than
one piece of code (multiple entry points) for the constructor for A.
In general, you would have to produce one entry point for each subset
of A's virtual base classes (both direct and indirect).  The entry point
chosen by the caller encodes the "current state of the world" wrt to which
virtual bases have been initialized.  The original caller (the code which
is instantiating a "complete object") will call the constructor entry point
for the most derived class corresponding to "no virtual bases have been
initialized".  After that, each constructor entry point can choose the
correct entry points to call in constructors for its superclasses, based on
its initial assumption and the superclass constructors which it has already
called.  (Yes, that was an induction that just went by.)

The problem, here, of course, is that the number of entry points needed for
the constructors of a class A is exponential in the number of virtual base
classes which A has.  In general, that seems to be a rather severe trading
of space for time, compared to the alternative of passing the current state
of virtual base class initialization as one or more hidden parameters to the
constructors.  It is interesting that the ARM's rule for virtual base class
initialization appears to enable an special case, though.  With the ARM's
rule, the current state of virtual base class initialization can be reduced
to ALL or NONE.  This appears to allow an implementation in which each
class having virtual base classes could have just two entry points per
constructor.  One, which is called only when this class is the class of
the "complete object", initializes all virtual base classes, then calls
constructors for its immediate base classes.  The other, which is called
by constructors of classes derived from this one, just calls constructors
for its immediate *nonvirtual* bases.

Is this the reason that the ARM's rule is as it is?  Does anybody (who is
listening) know?  The only (non cfront) compiler whose generated code I
have looked at appears to use the hidden parameters method.  Can anybody
tell me what cfront does?

---------------------------------------------------------------------
Eric Dittert  Intel Corp.,  Architecture Development Laboratory
(erd@ashland.intel.com)
*** Opinions expressed are mine and not necessarily Intel's. ***
---------------------------------------------------------------------




Author: landauer@morocco.Eng.Sun.COM (Doug Landauer)
Date: 22 Apr 1992 01:59:13 GMT
Raw View
(Cross-posted to comp.std.c++, as it's definitely a standards
issue.  Followups directed there.)

> Is the following class D class abstract?
> struct V { virtual void f()=0;};
> struct B : public virtual V { void f();};
> struct C : public virtual V {};
> struct D : public B, public C {};
> I always assumed D was _not_ abstract ...
>
> The function V::f() above is pure, but it is dominated by B::f(),
> so it cannot be called virtually, and hence cannot contribute to
> the issue of whether D is abstract or not.

This confusing issue remains confusing (to me, at least) in the C++
committee's current draft working paper.  The key sentence there
(it's also in the ARM) seems to be a little later in section 10.3
("Abstract Classes"):
   "Pure virtual functions are inherited as pure virtual functions."
I've been told that this is intended to mean
   "Pure virtual functions are inherited as if they'd been re-written
    in the derived class as pure virtual functions."
In other words, your class "C", as written, would have the same meaning
if it had been written:
   struct C : public virtual V { void f() = 0; };
And "D", given the choice of a normal inheritance of B::f() and this
possibly-surprising, implicit rewrite rule for the pure virtual function
it inherits from "C", would be the same as if you'd written
   struct D : public B, public C { void f() = 0; };

Questions for committee members:
1. Is this indeed how it is supposed to work?
2. Is this how it should work?
3. If so, could we make the wording of this section more explicit, so that
   it's clear what happens in cases of multiple inheritance?  Maybe replace
   that sentence with something that means the same as this, but
   states it more elegantly ? :

 Pure virtual functions are inherited as if they'd been
 re-written in the derived class as pure virtual functions.
 If a class is inherits a member function from multiple base
 classes, and does not override that function, and that function
 is a pure virtual function in at least one of the base classes,
 then it is a pure virtual function in the derived class.
--
Doug Landauer -- landauer@eng.sun.com
SMI[STE]->SunPro::Languages.PE(C++);




Author: sakkinen@jyu.fi (Markku Sakkinen)
Date: Wed, 22 Apr 1992 09:16:08 GMT
Raw View
In article <kv9i3hINN5f9@exodus.Eng.Sun.COM> landauer@morocco.Eng.Sun.COM (Doug Landauer) writes:
>(Cross-posted to comp.std.c++, as it's definitely a standards
>issue.  Followups directed there.)
>
>> Is the following class D class abstract?
>> struct V { virtual void f()=0;};
>> struct B : public virtual V { void f();};
>> struct C : public virtual V {};
>> struct D : public B, public C {};
>> I always assumed D was _not_ abstract ...
>>
>> The function V::f() above is pure, but it is dominated by B::f(),
>> so it cannot be called virtually, and hence cannot contribute to
>> the issue of whether D is abstract or not.
>
>This confusing issue remains confusing (to me, at least) in the C++
>committee's current draft working paper.  The key sentence there
> ...

If any new rule has the consequence that B::f does _not_ dominate
V::f and that D is abstract, that rule is badly broken and should
be urgently corrected.  Otherwise pure virtuals cannot work smoothly
together with "fork-join" inheritance like this.

----------------------------------------------------------------------
Fight against terrorism: destroy the army HQ in Belgrade!

Markku Sakkinen (sakkinen@jytko.jyu.fi)
       SAKKINEN@FINJYU.bitnet (alternative network address)
Department of Computer Science and Information Systems
University of Jyvaskyla (a's with umlauts)
PL 35
SF-40351 Jyvaskyla (umlauts again)
Finland
----------------------------------------------------------------------




Author: sakkinen@jyu.fi (Markku Sakkinen)
Date: Thu, 23 Apr 1992 10:18:13 GMT
Raw View
A new question closely related to the preceding discussion:
I could not find anything in the ARM that would forbid redefining
an inherited "concrete" member function as pure virtual.
Example:

struct V { virtual void f();};
struct B : public virtual V { void f()=0;};

B would of course be abstract then.  Do you consider that legal?
(What -- a question on which I have no strong preconceived opinion?
Well, unless somebody presents good arguments to the contrary,
I cannot see why this possibility should be ruled out, although
it seems a little strange at first.)

----------------------------------------------------------------------
Fight against terrorism: destroy the army HQ in Belgrade!

Markku Sakkinen (sakkinen@jytko.jyu.fi)
       SAKKINEN@FINJYU.bitnet (alternative network address)
Department of Computer Science and Information Systems
University of Jyvaskyla (a's with umlauts)
PL 35
SF-40351 Jyvaskyla (umlauts again)
Finland
----------------------------------------------------------------------




Author: maxtal@extro.ucc.su.OZ.AU (Tim Lister)
Date: Thu, 23 Apr 1992 16:28:55 GMT
Raw View
In article <1992Apr23.101813.14043@jyu.fi> sakkinen@jyu.fi (Markku Sakkinen) writes:
>A new question closely related to the preceding discussion:
>I could not find anything in the ARM that would forbid redefining
>an inherited "concrete" member function as pure virtual.
>Example:
>
>struct V { virtual void f();};
>struct B : public virtual V { void f()=0;};
>
>B would of course be abstract then.  Do you consider that legal?
 <humility deleted|grin>
>
>Markku Sakkinen (sakkinen@jytko.jyu.fi)

Very interesting. Let me think about it.
Here's something else:

To gain direct control over initialisation of virtual bases,
some compilers allow ctor-constructors to include
*indirect* virtual bases in the initialiser list, as in
D::D(int x) : V(x) {}  // V not direct base of D, using our std example
This IMHO is forbidden by ARM.
However, it has some merit.

Now: really this means V acts as if directly inherited.
What are the implications for name dominance?
For abstract virtual bases?

Question: is it a good thing?

 John Skaller, Maxtal P/L






Author: landauer@morocco.Eng.Sun.COM (Doug Landauer)
Date: 23 Apr 1992 17:38:36 GMT
Raw View
> I could not find anything in the ARM that would forbid redefining
> an inherited "concrete" member function as pure virtual.
> struct V { virtual void f();};
> struct B : public virtual V { void f()=0;};
> B would of course be abstract then.  Do you consider that legal?

Yes.  This one was made explicitly legal in the draft working papers,
since about a year ago -- section 10.3, paragraph 4 (currently) consists of:

 An abstract class may be derived from a class that is not
 abstract, and a pure virtual function may override a virtual
 function which is not pure.
--
Doug Landauer -- landauer@eng.sun.com
SMI[STE]->SunPro::Languages.PE(C++);




Author: sakkinen@jyu.fi (Markku Sakkinen)
Date: Fri, 24 Apr 1992 08:00:31 GMT
Raw View
In article <1992Apr24.072038.16707@jyu.fi> I just wrote:
> ...
>On the contrary, the ARM (p. 293) _requires_ all virtual bases to be
>initialised by the most derived class (if they have constructors
>and all constructors need arguments).  As noted in my new article
>in Computing Systems, this requirement goes too far, e.g.
>
>struct A { A(int); ... };
>struct B: virtual A { B(int i) : A(i) {} ... };
>struct C: B { ... };
>
>Every constructor of C must explicitly invoke the constructors
>of both B and A!
> ...

I just realised that it can get even trickier than that.
If A has also another constructor that needs no arguments,
then the constructors of C need not bother about A, BUT in that case
this default constructor gets tacitly invoked, since according to the
same page of the ARM, 'A(i)' in the constructor of B is simply ignored.
Surprise!

----------------------------------------------------------------------
Fight against terrorism: destroy the army HQ in Belgrade!

Markku Sakkinen (sakkinen@jytko.jyu.fi)
       SAKKINEN@FINJYU.bitnet (alternative network address)
Department of Computer Science and Information Systems
University of Jyvaskyla (a's with umlauts)
PL 35
SF-40351 Jyvaskyla (umlauts again)
Finland
----------------------------------------------------------------------




Author: maxtal@extro.ucc.su.OZ.AU (Tim Lister)
Date: Fri, 24 Apr 1992 19:01:12 GMT
Raw View
In article <1992Apr24.072038.16707@jyu.fi> sakkinen@jyu.fi (Markku Sakkinen) writes:
>In article <1992Apr23.162855.24616@ucc.su.OZ.AU> maxtal@extro.ucc.su.OZ.AU (Tim Lister) writes:
>
>> ...
>>Here's something else:
>>
>>To gain direct control over initialisation of virtual bases,
>>some compilers allow ctor-constructors to include
>>*indirect* virtual bases in the initialiser list, as in
>>D::D(int x) : V(x) {}  // V not direct base of D, using our std example
>>This IMHO is forbidden by ARM.
>>However, it has some merit.
>
>On the contrary, the ARM (p. 293) _requires_ all virtual bases to be
>initialised by the most derived class (if they have constructors
>and all constructors need arguments).  As noted in my new article

 Could you send this to me email (if convenient)?
 And yes, I agree ARM requires this.

>in Computing Systems, this requirement goes too far, e.g.
>
>struct A { A(int); ... };
>struct B: virtual A { B(int i) : A(i) {} ... };
>struct C: B { ... };
>
>Every constructor of C must explicitly invoke the constructors
>of both B and A!

 YES. But C is not ALLOWED to initialise A,
 because A is not an immediate base (ARM p290)
 SO: it must initialise it, but its not allowed to!
 Therefore, your class C is not legal.

Summary: As I understand it, objects of your class C cannot be constructed.
 You MUST choose :
 struct A { A(); A(int);...}; // forced to add default
  OR
 struct C: B, virtual public A { C(...) : A(int) ...}
 // forced to inherit
  OR
 (your example works because you use a compiler with an extension
 that allows initialisation of non-immediate bases).

*That* is the problem. You shouldn't need to know what the non-immediate
bases are, but you must know. At least ARM requires that you explicitly
inherit the class (to make it immediate base class so that you CAN
actually initialise it) But then, your NAME-DOMINANCE structure
is destroyed. That is a good argument for allowing the extension.

But there is an alternative-just initialise the VB once: whoever
gets in first (by existing rules?) controls the initialisation.

Now: I'm not sure what the correct approach is here.
ANSI committee and other experts: what is the current position?

 John Skaller, Maxtal P/L.







Author: jimad@microsoft.com (Jim ADCOCK)
Date: 24 Apr 92 19:02:16 GMT
Raw View
In article <1992Apr23.162855.24616@ucc.su.OZ.AU> maxtal@extro.ucc.su.OZ.AU (Tim Lister) writes:
|To gain direct control over initialisation of virtual bases,
|some compilers allow ctor-constructors to include
|*indirect* virtual bases in the initialiser list, as in
|D::D(int x) : V(x) {}  // V not direct base of D, using our std example
|This IMHO is forbidden by ARM.

Hm, I remember it being required by ARM.

|Now: really this means V acts as if directly inherited.

I disagree, this is an initialization issue, not an inheritence issue.

|What are the implications for name dominance?

None, initialization and inheritence are differing issues.

|For abstract virtual bases?

None.  However, no matter how one initializes a virtual base, implicitly
or explicitly, there are interesting issues of dominance vs abstract
virtual functions, as you and others have pointed out.  Which compilers
don't seem to agree upon.





Author: db@tigerden.Eng.Sun.COM (David Brownell)
Date: 25 Apr 1992 04:55:11 GMT
Raw View
(Returning to the original question, not initializers!)

> > Is the following class D class abstract?
> > struct V { virtual void f()=0;};
> > struct B : public virtual V { void f();};
> > struct C : public virtual V {};
> > struct D : public B, public C {};
> > I always assumed D was _not_ abstract ...

> This confusing issue remains confusing (to me, at least) in the C++
> committee's current draft working paper.  The key sentence there
> (it's also in the ARM) seems to be a little later in section 10.3
> ("Abstract Classes"):
>    "Pure virtual functions are inherited as pure virtual functions."

The historical preface there makes it clear (to me! :-) that this is
designed to fix a botch with an earlier implementation, which had a
bug in that it forced users to constantly redefine pure virtuals
as such.  Much like the newer incarnation, where users of MI are
always needing to redefine nonpure virtuals as such!

I'll agree that this is underspecified in the ARM, but I can't agree
that the class "D" above should be abstract.

--
David Brownell                        db@Eng.Sun.COM.
Distributed Object Management

REAL CYCLISTS don't take abuse from motorists, they drop them.




Author: sakkinen@jyu.fi (Markku Sakkinen)
Date: Sat, 25 Apr 1992 07:27:09 GMT
Raw View
In article <1992Apr24.190112.6335@ucc.su.OZ.AU> maxtal@extro.ucc.su.OZ.AU (Tim Lister, or actually John Skaller) writes:
>In article <1992Apr24.072038.16707@jyu.fi> sakkinen@jyu.fi (Markku Sakkinen) writes:
>>In article <1992Apr23.162855.24616@ucc.su.OZ.AU> maxtal@extro.ucc.su.OZ.AU (Tim Lister, or actually John Skaller) writes:
>>> ...
>>On the contrary, the ARM (p. 293) _requires_ all virtual bases to be
>>initialised by the most derived class (if they have constructors
>>and all constructors need arguments).  As noted in my new article
>
> Could you send this to me email (if convenient)?
> And yes, I agree ARM requires this.
>
>>in Computing Systems, this requirement goes too far, e.g.
>>
>>struct A { A(int); ... };
>>struct B: virtual A { B(int i) : A(i) {} ... };
>>struct C: B { ... };
>>
>>Every constructor of C must explicitly invoke the constructors
>>of both B and A!
>
> YES. But C is not ALLOWED to initialise A,
> because A is not an immediate base (ARM p290)
> SO: it must initialise it, but its not allowed to!
> Therefore, your class C is not legal.

You must be right, and so I have interpreted this situation the wrong way
even in my paper!  Indeed, the example in the ARM declares an indirect
virtual base class also as an direct virtual base, evidently for this
very purpose.  However, the rationale for disallowing the initialisation
of indirect bases says that it prevents multiple initialisation:
it would be prevented anyway by the special rule for virtual base classes.

>Summary: As I understand it, objects of your class C cannot be constructed.
> You MUST choose :
> struct A { A(); A(int);...}; // forced to add default
>  OR
> struct C: B, virtual public A { C(...) : A(int) ...}
> // forced to inherit
>  OR
> (your example works because you use a compiler with an extension
> that allows initialisation of non-immediate bases).

I tried now on the HP compiler, which is based on AT&T Release 2.1.
It did not complain -- interesting!  But even so, either the
inheritance chain from the virtual base class must be public (or protected),
or that base class must be also directly inherited.  An error in my paper
anyway.

>*That* is the problem. You shouldn't need to know what the non-immediate
>bases are, but you must know. At least ARM requires that you explicitly
>inherit the class (to make it immediate base class so that you CAN
>actually initialise it) But then, your NAME-DOMINANCE structure
>is destroyed. That is a good argument for allowing the extension.

I agree with you that this is an unpleasant situation.
Fortunately however, it does _not_ disturb name dominance.
The reason is that a dominance relationship between two classes
in C++ is exactly the same as an inheritance relationship;
it cannot be different from the viewpoint of different common
descendants, no matter how complicated the further derivations are.
-- You have probably been thinking on the lines of
"inferential distance" or something similar.  In such models,
if we added to a linear inheritance chain A - B - C - D a direct
link A - D, then the features of A will dominate those of B in D.

>But there is an alternative-just initialise the VB once: whoever
>gets in first (by existing rules?) controls the initialisation.

This is exactly what I had recommended in my paper.
I don't think any such ordering rules exist -- C++ is much better than
most other MI languages in the respect that it does not try to linearise
the inheritance graph -- but they would not be too difficult to make.

>Now: I'm not sure what the correct approach is here.
>ANSI committee and other experts: what is the current position?

----------------------------------------------------------------------
Fight against terrorism: destroy the army HQ in Belgrade!

Markku Sakkinen (sakkinen@jytko.jyu.fi)
       SAKKINEN@FINJYU.bitnet (alternative network address)
Department of Computer Science and Information Systems
University of Jyvaskyla (a's with umlauts)
PL 35
SF-40351 Jyvaskyla (umlauts again)
Finland
----------------------------------------------------------------------




Author: maxtal@extro.ucc.su.OZ.AU (Tim Lister)
Date: Sat, 25 Apr 1992 15:09:20 GMT
Raw View
In article <1992Apr25.072709.20818@jyu.fi> sakkinen@jyu.fi (Markku Sakkinen) writes:
struct A { A(int); ... };
struct B: virtual A { B(int i) : A(i) {} ... };
struct C: B { ... };
>
>>But there is an alternative-just initialise the VB once: whoever
>>gets in first (by existing rules?) controls the initialisation.
>
>This is exactly what I had recommended in my paper.
>I don't think any such ordering rules exist -- C++ is much better than
>most other MI languages in the respect that it does not try to linearise
>the inheritance graph -- but they would not be too difficult to make.
>
>>Now: I'm not sure what the correct approach is here.
>>ANSI committee and other experts: what is the current position?
>
After I posted the post to which you reply here, I rode out into
the night ... and had the following idea:

 "If two classes D and B are derived from a common virtual base V,
 and B is a direct or indirect base of D, the we say D *dominates* B.
 If there is a class, *not necessarily the most derived class*,
 which is derived from V virtually and explicitly,
 and which is NOT dominated by any other class
 which is also derived from V virtually and explicitly,
 then V will be initialised by that class, otherwise initialisation
 is ambiguous and the class cannot be instantiated."

 In other words, the name dominance rule provides the correct
 solution to the problem already-it just needs to be upgraded
 to apply to this situation.
 Note that the *initialisation* does not need to be explicit.

 Here's an example:
 struct V { V(int);};
 struct B : public virtual V { B(int i) : V(i) {} };
 struct C : public B { C(int i) : B(i) {} }; // V not required
 struct X : public virtual V {} // cannot be instantiated
 struct D : public X, public virtual V { D(int i) : V(i) {} };
 // V required in D so D dominates X wrt V

 Can I have some comment: is this the correct solution?

 John Skaller, Maxtal P/L






Author: maxtal@extro.ucc.su.OZ.AU (Tim Lister)
Date: Sat, 25 Apr 1992 15:59:24 GMT
Raw View
In article <kvhphfINN8h3@exodus.Eng.Sun.COM> db@tigerden.Eng.Sun.COM (David Brownell) writes:
>
>> > Is the following class D class abstract?
>> > struct V { virtual void f()=0;};
>> > struct B : public virtual V { void f();};
>> > struct C : public virtual V {};
>> > struct D : public B, public C {};
>> > I always assumed D was _not_ abstract ...
>
>> This confusing issue remains confusing (to me, at least) in the C++
>> committee's current draft working paper.  The key sentence there
>> (it's also in the ARM) seems to be a little later in section 10.3
>> ("Abstract Classes"):
>>    "Pure virtual functions are inherited as pure virtual functions."
>
>The historical preface there makes it clear (to me! :-) that this is
>designed to fix a botch with an earlier implementation, which had a
>bug in that it forced users to constantly redefine pure virtuals
>as such.  Much like the newer incarnation, where users of MI are
>always needing to redefine nonpure virtuals as such!
>
>I'll agree that this is underspecified in the ARM, but I can't agree
>that the class "D" above should be abstract.
>
>--
>David Brownell                        db@Eng.Sun.COM.
>Distributed Object Management

Can I assume you are uncommitted as to whether D *should* be abstract-
irrespective of what the ARM says?

I believe it is very important that this issue is resolved
(preferably making D concrete).

Please inspect ARM 10.10c, p233-235.
The ability to define a virtual in one sibling and use it in another
is what makes *incremental* development possible in C++.
Mere deriving from a class allows *layered* development,
better than complete rewrites but really a brain dead method
compared with incremental development-thats real reuse.
The reason is that you can DELAY binding till whenever you like.

Now if a class like D is abstract, just because your base is abstract,
you must chose to abandon either incremental development (never!!)
or abstraction (less important-only a convenience and safety check)
SO-you abandon abstraction. You must. In other words,
abstraction is useless (except in SI schemes, or MI schemes
not using virtual bases-in other words, where the increments
just happen to be layers. NO: where the increments MUST be layers.)

But I don't WANT to give up abstraction. I like it.
And there is NO REASON why I should that I know of
(there might be a reason-tell me if you think of one).

Look again at ARM p234. Surely it makes sense for
W::f and W::g to be pure?  Indeed, I would consider it
almost mandatory for class libraries that separate abstract
interface classes be defined for all important concepts.
That way, and only that way, can algorithms be reusable
if the implementation of a class changes.

And that applies to the power or 2 (or more) if you are doing
multiple inheritance. You WANT to build your concept (interface)
first using abstract clases, then provide the implementation
by grabbing implementations of the bits from the library.

What use is this if the result is still abstract (like D?) ??
What sense is it when all the base virtuals are actually
bound, as you intended, and as can be done without abstract classes,
that you cant make a function which can never be called virtually
pure?

 John Skaller, MAXTAL P/L






Author: sakkinen@jyu.fi (Markku Sakkinen)
Date: Thu, 30 Apr 1992 13:32:17 GMT
Raw View
In article <1992Apr25.150920.8455@ucc.su.OZ.AU> maxtal@extro.ucc.su.OZ.AU (Tim Lister -- actually John Skaller) writes:
>In article <1992Apr25.072709.20818@jyu.fi> sakkinen@jyu.fi (Markku Sakkinen) writes:
>> ...

>After I posted the post to which you reply here, I rode out into
>the night ... and had the following idea:
>
> "If two classes D and B are derived from a common virtual base V,
> and B is a direct or indirect base of D, the we say D *dominates* B.
> If there is a class, *not necessarily the most derived class*,
> which is derived from V virtually and explicitly,
> and which is NOT dominated by any other class
> which is also derived from V virtually and explicitly,
> then V will be initialised by that class, otherwise initialisation
> is ambiguous and the class cannot be instantiated."
> ...

Although this would be a clear improvement to the current rules,
I think that the effect of virtuality can propagate too far even under
your proposal:  just think of a single-inheritance chain where the
derivations are declared virtual for the sake of future extensibility.
(BTW, what does 'explicitly' mean here -- how could a C++ class be
_implicitly_ derived from another one?)

I tried to make a simple improved suggestion, but it turned out to
become quite complicated, and then I succeeded to delete a lot of
freshly-written text.  Perhaps a little later.

In the meantime, it is important to point out that there
are worse problems than initialisation in the current C++ rules on
"virtual" base classes.  I have elaborated on such questions in my recent
paper, but perhaps a simple example suffices:
  struct A { ... };
  struct B: virtual A { ... };
  struct C: B { ... };
  struct D: B { ... };
  struct E: C, D { ... };
The two B subobjects in an E object will share a common A part!

----------------------------------------------------------------------
Fight against terrorism: destroy the army HQ in Belgrade!

Markku Sakkinen (sakkinen@jytko.jyu.fi)
       SAKKINEN@FINJYU.bitnet (alternative network address)
Department of Computer Science and Information Systems
University of Jyvaskyla (a's with umlauts)
PL 35
SF-40351 Jyvaskyla (umlauts again)
Finland
----------------------------------------------------------------------




Author: erd@tigard.intel.com (Eric Dittert)
Date: 1 May 92 23:31:35 GMT
Raw View
I subscribed to this newsgroup a couple of days ago with the intention of
starting a discussion on multiply virtually inherited base classes
with respect to a) pure virtual functions, and b) initialization.
I guess this is a case of just-in-time news reading...

In previous postings, there has been considerable discussion on whether
a derived class can/should provide a mem-initializer for a virtually
inherited base class that is not inherited directly.  It has been
observed that the ARM (12.6.2) says that this is disallowed.
However, the stated purpose of this immediate-base-only-initialization-
rule (IBOIR) is to prevent multiple initializations by using a syntax
rule enforceable at compile time.
For single inheritance, and for multiple, non-virtual inheritance,
the IBOIR suffices to do this.  But for a multiply virtually inherited
class V, in general, there will be multiple derived class constructors
which will attempt to initialize V.  Any C++ compiler implementors who
are listening can set me straight if I am wrong, but it would seem to me
that a run time mechanism is necessary to prevent multiple initializations
in this case. The conclusion is that for a multiply virtually inherited
class, the IBOIR is not useful; IMHO, it simply should not apply in this
case.

Another topic of discussion has been which class in an inheritance
structure containing virtual inheritance should initialize
the virtual base class.  So far there seem to be three candidates.
 a) The ARM says that virtual base classes are initialized
    by the most derived class.
 b) Several writers suggested that the initialization used
    should be the first one encountered, presumably in the
    usual post-order traversal order for constructor execution.
 c) J. Skaller has suggested a rule based on extending the
    notion of dominance to classes.

Based on an experience trying to design a class library in which
multiple virtual inheritance would have been a useful tool, I find
the ARM's rule highly unsatisfactory.  One flaw seems obvious: the
requirement to provide initialization for the virtual base class
propogates all the way down the inheritance structure.  This seems
particularly inappropriate in the case of a class outside the library
which inherits from a single class in the library.  That class must
provide initialization for the virtual base class, which it otherwise
has no need or desire to know about.

Another problem with the rule as stated in the ARM is that it impacts
even single virtual inheritance.  That is, even if a class inherits
from a class V along only one path, if the (direct) inheritance of V
was tagged as virtual, then the derived class must provide for the
initialization of V.

The FCFS rule (b) is an interesting alternative.  The things that I
dislike about it are
 (i) The programmer must use declaration order to assure that
     the correct initialization of a virtual base class is done.
     This is an awkward method.
 (ii) The programmer may have to introduce an otherwise
     unnecessary direct inheritances of virtual base classes
     in order to achieve the correct initializations, particularly
     if there is more than one virtual base class to deal with.
Provided that classes in a class library were only inherited from
singly, these unpleasantnesses could be at least be confined to
the library (in contrast to the ARM's rule, which propagates dreck
into the library user's code even for single inheritance usage).
This rule also degenerates appropriately in the case of single
virtual inheritance; nothing special is required if a class inherits
from a base virtually along only one path.

The third alternative (c) has some definitional problems.  I hope
Mr. Skaller will post a revised version.

And now, of course, I have my own suggestion to offer.  Again based
on my attempt to build a class library using multiple virtual inheritance,
this is the way that I *wanted* things to work.

Context:
-------
Suppose we have a class C which inherits virtually from a class V
along one or more paths.  Let J be the *least* derived class which
inherits virtually from V along all the paths that C does.  (There
will be a unique such class; the proof is left to the reader.)

Desired effect:
--------------
J provides the initialization of V.

Syntax rule:
-----------
If a class X has two or more base classes B1,...,Bn, and
two or more of the classes B1,...,Bn inherit a class V
virtually (either directly or indirectly), then each
constructor of class X must provide initialization for
V either through the existence of a default
constructor for V or via a 'mem-initializer'.

Otherwise, X provides initialization for only its
immediate base classes.

Execution of constructors:
-------------------------
Much the same as in the ARM: during execution of a
constructor for a derived class, constructors for
virtual bases are invoked first, then others in
declaration order; constructors called on an instance
of a virtual base that has already been initialized
have no effect.

Notes:
-----
In the inheritance structure for C, the syntax rule
may have required several classes to provide
initialization for V.  J will be the first such class
encountered in the usual constructor execution order.

If V is inherited virtually along only a single path,
then the syntax rule does not require anything special
of any class, and  J is the class which inherits from
V virtually and directly.  In other words, things work
exactly as if the inheritance were not virtual.

These rules would result in a different initialization
order than that given in the ARM.  However, it would still
be the case that each base class was initialized before
its descendants.

J seems to me to be the 'natural' place to specify the
initialization for V in the following sense. Virtual
inheritance makes a difference in the semantics
of the language *only* if there is a multiple virtual inheritance.
Other ambiguities engendered by multiple inheritance, such as
name clashes, are usually resolved in the class which creates
the conflict by inheriting multiply.  (More rarely, they are
resolved in some subclass of that class.)  The syntax rule
given above targets just those classes which create an
ambiguity wrt how a virtual base class should be initialized.

Comments?

---------------------------------------------------------------------
Eric Dittert  Intel Corp.,  Architecture Development Laboratory
(erd@ashland.intel.com)
*** Opinions expressed are mine and not necessarily Intel's. ***
---------------------------------------------------------------------




Author: hf@informatik.uni-kl.de (Harald Fuchs)
Date: 5 May 92 09:08:22 GMT
Raw View
In article <erd.704763095@tigard> erd@tigard.intel.com (Eric Dittert) writes:

   Syntax rule:
   -----------
   If a class X has two or more base classes B1,...,Bn, and
   two or more of the classes B1,...,Bn inherit a class V
   virtually (either directly or indirectly), then each
   constructor of class X must provide initialization for
   V either through the existence of a default
   constructor for V or via a 'mem-initializer'.
                               ^^^
                               base class initializer

   Otherwise, X provides initialization for only its
   immediate base classes.

Yes, that's IMHO The Right Thing. Since it would invalidate existing
code only in the case where X differs from the most derived class,
there might even be a small chance that it makes its way into the ANSI
standard.
--

Harald Fuchs <hf@informatik.uni-kl.de>




Author: sakkinen@jyu.fi (Markku Sakkinen)
Date: Tue, 5 May 1992 14:04:06 GMT
Raw View
In article <erd.704763095@tigard> erd@tigard.intel.com (Eric Dittert) writes:
> ...
>However, the stated purpose of this immediate-base-only-initialization-
>rule (IBOIR) is to prevent multiple initializations by using a syntax
>rule enforceable at compile time.
>For single inheritance, and for multiple, non-virtual inheritance,
>the IBOIR suffices to do this.  But for a multiply virtually inherited
>class V, in general, there will be multiple derived class constructors
>which will attempt to initialize V.  Any C++ compiler implementors who
>are listening can set me straight if I am wrong, but it would seem to me
>that a run time mechanism is necessary to prevent multiple initializations
>in this case. The conclusion is that for a multiply virtually inherited
>class, the IBOIR is not useful; IMHO, it simply should not apply in this
>case.

Without being a compiler implementor, it seems obvious to me that
a compile-time mechanism is possible in principle, since the exact
class of every object to be constructed is statically known.
However, the mechanism would probably be quite complicated.

>Another topic of discussion has been which class in an inheritance
>structure containing virtual inheritance should initialize
>the virtual base class.  So far there seem to be three candidates.
> ...

>Based on an experience trying to design a class library in which
>multiple virtual inheritance would have been a useful tool, I find
>the ARM's rule highly unsatisfactory.  One flaw seems obvious: the
>requirement to provide initialization for the virtual base class
>propogates all the way down the inheritance structure.  This seems
>particularly inappropriate in the case of a class outside the library
>which inherits from a single class in the library.  That class must
>provide initialization for the virtual base class, which it otherwise
>has no need or desire to know about.

I recognised that flaw in my paper;  nice to see it confirmed
by experience.

> ...
>And now, of course, I have my own suggestion to offer.  Again based
>on my attempt to build a class library using multiple virtual inheritance,
>this is the way that I *wanted* things to work.

As I said in my last posting (or a recent one anyway), the basic definition
of "virtual" base classes is so defective that it is hardly possible
to devise any fully satisfactory principle of initialisation on top of it.
But your attempt is good.

>Context:
>-------
>Suppose we have a class C which inherits virtually from a class V
>along one or more paths.  Let J be the *least* derived class which
>inherits virtually from V along all the paths that C does.  (There
>will be a unique such class; the proof is left to the reader.)

I think we have to elaborate yet a bit on this.
Probably I guess your intent, but if we take 'path' strictly,
J would always be equal to C if C has any virtual base classes!
How about:
  Let J be the least derived class that *is on* all paths along which
  C inherits virtually from V.

Note that, according to the current rules of C++, 'inherits V virtually
along a path' must mean that the direct subclass of V on that path
is virtually derived;  the virtuality or non-virtuality of the rest
of the path does not matter.

>Desired effect:
>--------------
>J provides the initialization of V.
>---------------------------------------------------------------------
> ...

Looks like a definite improvement to the current rules.
Of course, it could be applied even together with the IBOIR rule:
that would only require additional direct virtual base declarations.

I think that John Skaller's proposal based on dominance
was in some ways even better.
Take the most basic situation: B and C both virtually derived from A,
and D derived from B and C (the lozenge we have seen in almost all papers).
If only B or only C initialises A explicitly, or if neither does that
and A has a default constructor, then one would not like to _require_ D
to do anything about the initialisation of A (perhaps not even to allow it).
However, here we would get the additional complication
that the situation may be different with different constructors of A,
if they in turn invoke different constructors of B and C.

----------------------------------------------------------------------
Markku Sakkinen (sakkinen@jytko.jyu.fi)
       SAKKINEN@FINJYU.bitnet (alternative network address)
Department of Computer Science and Information Systems
University of Jyvaskyla (a's with umlauts)
PL 35
SF-40351 Jyvaskyla (umlauts again)
Finland
----------------------------------------------------------------------




Author: sakkinen@jyu.fi (Markku Sakkinen)
Date: Tue, 5 May 1992 14:30:56 GMT
Raw View
In article <1992May5.140406.17542@jyu.fi> I just wrote:
>In article <erd.704763095@tigard> erd@tigard.intel.com (Eric Dittert) writes:
>> ...
>>Suppose we have a class C which inherits virtually from a class V
>>along one or more paths.  Let J be the *least* derived class which
>>inherits virtually from V along all the paths that C does.  (There
>>will be a unique such class; the proof is left to the reader.)
>
>I think we have to elaborate yet a bit on this.
>Probably I guess your intent, but if we take 'path' strictly,
>J would always be equal to C if C has any virtual base classes!
>How about:
>  Let J be the least derived class that *is on* all paths along which
>  C inherits virtually from V.

That was perhaps excessive nitpicking;  sorry about that.
The original formulation is not really wrong.

----------------------------------------------------------------------
Markku Sakkinen (sakkinen@jytko.jyu.fi)
       SAKKINEN@FINJYU.bitnet (alternative network address)
Department of Computer Science and Information Systems
University of Jyvaskyla (a's with umlauts)
PL 35
SF-40351 Jyvaskyla (umlauts again)
Finland
----------------------------------------------------------------------




Author: erd@tigard.intel.com (Eric Dittert)
Date: 5 May 92 16:27:28 GMT
Raw View
In <1992Apr30.133217.4604@jyu.fi> sakkinen@jyu.fi (Markku Sakkinen) writes:

>In the meantime, it is important to point out that there
>are worse problems than initialisation in the current C++ rules on
>"virtual" base classes.  I have elaborated on such questions in my recent
>paper, but perhaps a simple example suffices:
>  struct A { ... };
>  struct B: virtual A { ... };
>  struct C: B { ... };
>  struct D: B { ... };
>  struct E: C, D { ... };
>The two B subobjects in an E object will share a common A part!

If such sharing is not desired, why is the inheritance of A
by B virtual?

---------------------------------------------------------------------
Eric Dittert  Intel Corp.,  Architecture Development Laboratory
(erd@ashland.intel.com)
*** Opinions expressed are mine and not necessarily Intel's. ***
---------------------------------------------------------------------




Author: sakkinen@jyu.fi (Markku Sakkinen)
Date: Wed, 6 May 1992 05:14:50 GMT
Raw View
In article <erd.705083248@tigard> erd@tigard.intel.com (Eric Dittert) writes:
>In <1992Apr30.133217.4604@jyu.fi> sakkinen@jyu.fi (Markku Sakkinen) writes:
>
>>In the meantime, it is important to point out that there
>>are worse problems than initialisation in the current C++ rules on
>>"virtual" base classes.  I have elaborated on such questions in my recent
>>paper, but perhaps a simple example suffices:
>>  struct A { ... };
>>  struct B: virtual A { ... };
>>  struct C: B { ... };
>>  struct D: B { ... };
>>  struct E: C, D { ... };
>>The two B subobjects in an E object will share a common A part!
>
>If such sharing is not desired, why is the inheritance of A
>by B virtual?

Because that is normally the conceptually and semantically correct
alternative for public inheritance ("is-a").
Or, to be more concrete, perhaps class B was originally designed
for a fork-join situation:
    struct X: virtual A { ... };
    struct Y: B, X { ... };

Under the sharing vs. replication rules proposed in my paper,
it would never be possible for a class instance to share a base class
subobject with other instances of the same class.
So this is an example of how the existing rules make "virtuality"
too global.

Now it strikes me:  if the initialisation rule that you suggested
were to be applicable also to pathologic cases like the above,
it is not sufficient to speak about the _class_ J, but about
the _class occurrence_ or _subobject_, which is to initialise
an indirect virtual base class.  Here, the least derived _class_
which is on all virtual inheritance paths between A and E would
obviously be B!  (Since B itself is not inherited virtually,
the rule would not allow E to define its initialisation.)

----------------------------------------------------------------------
Markku Sakkinen (sakkinen@jytko.jyu.fi)
       SAKKINEN@FINJYU.bitnet (alternative network address)
Department of Computer Science and Information Systems
University of Jyvaskyla (a's with umlauts)
PL 35
SF-40351 Jyvaskyla (umlauts again)
Finland
----------------------------------------------------------------------