Simplify virtual inheritance rules

138 views
Skip to first unread message

Denis Kotov

unread,
Mar 18, 2017, 3:31:32 PM3/18/17
to ISO C++ Standard - Future Proposals

Hi everyone,


My proposal is to simplify virtual inheritance rules. Consider the following class hierarchy:




and the following code:

    class VirtualBase
    {
    public:
       VirtualBase(std::string & str)
       {

       }
    };

    class Derived0 : virtual public VirtualBase
    {
    public:
       Derived0(std::string & str)
          : VirtualBase(str)
       {

       }
    };

    class Derived1 : virtual public VirtualBase
    {
    public:
       Derived1(std::string & str)
          : VirtualBase(str)
       {

       }
    };

    class ComposedDerived
       : public Derived0
       , public Derived1
    {
    public:
       ComposedDerived(std::string & str)
          : Derived0(str)
          , Derived1(str)
          , VirtualBase(str)
       {

       }
    };

Let's inherit from ComposedDerived again:

    class ComposedDerivedDerived
       : public ComposedDerived
       , public NewClass
    {
    public:
       ComposedDerivedDerived(std::string & str)
         : VirtualBase(str)
       {

       }
    };

I want to simplify it like the following:

    class ComposedDerivedDerived
       : public ComposedDerived
       , public NewClass
    {
    public:
       ComposedDerivedDerived(std::string & str) // Here is no VirtualBase(str)
       {

       }
    };

Proposal is the following:
   Require to specify constructor for VirtualBase only if compiler find more the one path of virtual inheritance like for C3 (on diagram) or ComposedDerived (in code) classes

Ville Voutilainen

unread,
Mar 18, 2017, 3:45:04 PM3/18/17
to ISO C++ Standard - Future Proposals
On 18 March 2017 at 21:31, Denis Kotov <redr...@gmail.com> wrote:
I want to simplify it like the following:

    class ComposedDerivedDerived
       : public ComposedDerived
       , public NewClass
    {
    public:
       ComposedDerivedDerived(std::string & str) // Here is no VirtualBase(str)
       {

       }
    };

Proposal is the following:
   Require to specify constructor for VirtualBase only if compiler find more the one path of virtual inheritance like for C3 (on diagram) or ComposedDerived (in code) classes


ComposedDerivedDerived didn't initialize ComposedDerived in your original code or this modified code. Also, there's no reason that
a compiler could find an initialization for a virtual base, since that initialization may be in a separate translation unit that the compiler doesn't
see, so this idea doesn't work at all.

Denis Kotov

unread,
Mar 18, 2017, 5:18:26 PM3/18/17
to ISO C++ Standard - Future Proposals
Yes your are right I have misspelled it. Should be something like this:

    class ComposedDerivedDerived
           : public ComposedDerived
           , public NewClass
        {
        public:
           ComposedDerivedDerived(std::string & str) // Here is no VirtualBase(str)
               : ComposedDerived(str),
               , NewClass()
           {

           }
        };

But I do not see any problems with separate translation unit.
We should not care about another separate unit, it is responsibility of compiler that will support this rule to require an user calls a constructor for VirtualBase.
We should not care because according to this rule we know that constructor had been called above.
But if user want it can call it directly for backward compitability.

VirtualBase constructor should be required to call only when class inherits from two or more classes those inhertits virtually from VirtualBase class.

суббота, 18 марта 2017 г., 21:45:04 UTC+2 пользователь Ville Voutilainen написал:

Ville Voutilainen

unread,
Mar 18, 2017, 6:08:22 PM3/18/17
to ISO C++ Standard - Future Proposals
On 18 March 2017 at 23:18, Denis Kotov <redr...@gmail.com> wrote:
> But I do not see any problems with separate translation unit.

You're asking for this rule:

If the constructor I call initializes the virtual base, I won't have
to. That rule is unimplementable
in the presence of separate translation units.

Furthermore, you cannot know what value the constructor you call
(would) initializes the virtual base with.
Currently, the intermediate class you initialize will not initialize
the virtual base at all, so there's a good
chance that what you're proposing is also an ABI break.

T. C.

unread,
Mar 18, 2017, 6:55:24 PM3/18/17
to ISO C++ Standard - Future Proposals
I think OP's proposed rule is something like

If an indirect virtual base is a (direct or indirect) base of only one direct base, then you can omit initializing the virtual base. Presumably in that case you'd call the complete object constructor of said direct base so that it initializes the virtual base, rather than the base object constructor.

This wouldn't depend on the definition of the constructors at issue, only the classes, and won't run into separate translation issues. It does have plenty of other issues, though (e.g., handling of default constructible virtual bases, initialization order issues, etc., etc.), and I don't see how the headache is worth the minuscule benefit.

Denis Kotov

unread,
Mar 19, 2017, 8:22:28 AM3/19/17
to ISO C++ Standard - Future Proposals
Thanks for comment T. C.,

But I do not agree that it is not worth to do it.
Consider situation of very long chain of inheritance. Why in this chain user has to call every time constructor for VirtualBase ?
It violates an encapsulation. User in derived class does not have to know something about it parent long long ago ... It will just simplify development of class hierarchy and make class hierarchy looks like in Java or C# with one instance of Base class and similar rules.

Consider another situation where in long chain of inheritance somewhere user inherite VirtualBase implicitly with access modifier 'protected' or 'private'.
Why user should know something about parent class ?

воскресенье, 19 марта 2017 г., 0:55:24 UTC+2 пользователь T. C. написал:

Ville Voutilainen

unread,
Mar 19, 2017, 8:28:17 AM3/19/17
to ISO C++ Standard - Future Proposals
On 19 March 2017 at 14:22, Denis Kotov <redr...@gmail.com> wrote:
> Thanks for comment T. C.,
>
> But I do not agree that it is not worth to do it.
> Consider situation of very long chain of inheritance. Why in this chain user
> has to call every time constructor for VirtualBase ?


The language statically guarantees that the initialization of the
VirtualBase is performed. Your
suggestion turns that static guarantee into undefined behavior.

Denis Kotov

unread,
Mar 19, 2017, 8:55:08 AM3/19/17
to ISO C++ Standard - Future Proposals
Ville Voutilainen,

That is the point: We will anyway know that VirtualBase is performed, but by parent class where two VirtualBase (VC class) class inherites in class C3.
Class C5 already knows that class C3 initialized VirtualBase (VC class)
C5 class should not know anything about VirtualBase, because C3 has already called constructor for VirtualBase. It is useless to call in C5 constructor for VirtualBase if C5 does not what to do with parent classes.

воскресенье, 19 марта 2017 г., 14:28:17 UTC+2 пользователь Ville Voutilainen написал:

Denis Kotov

unread,
Mar 19, 2017, 9:07:05 AM3/19/17
to ISO C++ Standard - Future Proposals
To share my view more clear I have added a new picture:



суббота, 18 марта 2017 г., 21:31:32 UTC+2 пользователь Denis Kotov написал:

Ville Voutilainen

unread,
Mar 19, 2017, 9:07:26 AM3/19/17
to ISO C++ Standard - Future Proposals
On 19 March 2017 at 14:55, Denis Kotov <redr...@gmail.com> wrote:
> Ville Voutilainen,
>
> That is the point: We will anyway know that VirtualBase is performed, but by
> parent class where two VirtualBase (VC class) class inherites in class C3.

There are situations where the bases do not need to initialize the virtual base:
http://open-std.org/JTC1/SC22/WG21/docs/cwg_defects.html#1611

Ville Voutilainen

unread,
Mar 19, 2017, 9:24:35 AM3/19/17
to ISO C++ Standard - Future Proposals
On 19 March 2017 at 15:07, Ville Voutilainen
However, thanks for the clarification, so what we have here is

1) if the intermediate bases are non-abstract, they will have compiled
the virtual base initialization into them
2) therefore the more-derived class wouldn't need to do it

So we don't have a case of UB. We have cases where code that wasn't
previously valid will now be valid,
though:

struct B
{
private:
B(int) {}
friend class D;
};

struct D : virtual B // thou shalt not initialize me from a derived class
{
D() : B(42) {}
};

struct E : D
{
E() : B(42) {} // this attempt to initialize D is ill-formed
};

int main()
{
D d; // ok
E e; // we can't even get this far
}

With your proposal, E could skip the initialization of B and be well-formed.

Another problem with the proposal is that today it's possible to write
class hierarchies that
rely on the most-derived class being the one to initialize the virtual
base. This proposal
changes how such hierarchies behave.

Thiago Macieira

unread,
Mar 19, 2017, 1:58:14 PM3/19/17
to std-pr...@isocpp.org
The complete constructor must exist if the direct base class is not abstract.
In other words, the complete constructor must exist if a user is able to
construct that class.

The compiler will have to emit the base object constructor for all of those
classes, since another class down the line could choose to construct the
virtual base directly.

--
Thiago Macieira - thiago (AT) macieira.info - thiago (AT) kde.org
Software Architect - Intel Open Source Technology Center

Ville Voutilainen

unread,
Mar 19, 2017, 2:12:26 PM3/19/17
to ISO C++ Standard - Future Proposals
On 19 March 2017 at 19:58, Thiago Macieira <thi...@macieira.org> wrote:
>> The language statically guarantees that the initialization of the
>> VirtualBase is performed. Your
>> suggestion turns that static guarantee into undefined behavior.
>
> The complete constructor must exist if the direct base class is not abstract.
> In other words, the complete constructor must exist if a user is able to
> construct that class.
>
> The compiler will have to emit the base object constructor for all of those
> classes, since another class down the line could choose to construct the
> virtual base directly.


Right, so this proposal doesn't produce UB. That leaves the other
compatibility issues.

Thiago Macieira

unread,
Mar 19, 2017, 2:20:43 PM3/19/17
to std-pr...@isocpp.org
On domingo, 19 de março de 2017 11:12:25 PDT Ville Voutilainen wrote:
> > The compiler will have to emit the base object constructor for all of
> > those
> > classes, since another class down the line could choose to construct the
> > virtual base directly.
>
> Right, so this proposal doesn't produce UB. That leaves the other
> compatibility issues.

Which ones?

Does it make a difference in behaviour which base class actually called the
virtual base's construtor? Is there any situation in which it changes with
this proposal?

Ville Voutilainen

unread,
Mar 19, 2017, 2:32:05 PM3/19/17
to ISO C++ Standard - Future Proposals
On 19 March 2017 at 20:20, Thiago Macieira <thi...@macieira.org> wrote:
>> Right, so this proposal doesn't produce UB. That leaves the other
>> compatibility issues.
>
> Which ones?

See a couple of emails ago. The cases where the most-derived class
doesn't have access to the constructor
of the virtual base; that makes intentionally ill-formed cases well-formed.

> Does it make a difference in behaviour which base class actually called the
> virtual base's construtor? Is there any situation in which it changes with
> this proposal?

There may well be cases where the designer of the class hierarchy
intends to force the authors
of derived classes to provide a value to be used for initializing the
virtual base. It might also be the case
that that value is an invariant for the hierarchy, although I'm not
sure whether anyone writes such hierarchies.
Practically, that means passing 'this' up the hierarchy into the virtual base.

Thiago Macieira

unread,
Mar 19, 2017, 3:10:35 PM3/19/17
to std-pr...@isocpp.org
On domingo, 19 de março de 2017 11:32:04 PDT Ville Voutilainen wrote:
> On 19 March 2017 at 20:20, Thiago Macieira <thi...@macieira.org> wrote:
> >> Right, so this proposal doesn't produce UB. That leaves the other
> >> compatibility issues.
> >
> > Which ones?
>
> See a couple of emails ago. The cases where the most-derived class
> doesn't have access to the constructor
> of the virtual base; that makes intentionally ill-formed cases well-formed.

Right. This may have been used in existing code to limit inheritance in a tree
to only classes that are explicitly white-listed as friends at some point.

I'm wondering if now allowing this inheritance is a problem.

> > Does it make a difference in behaviour which base class actually called
> > the
> > virtual base's construtor? Is there any situation in which it changes with
> > this proposal?
>
> There may well be cases where the designer of the class hierarchy
> intends to force the authors
> of derived classes to provide a value to be used for initializing the
> virtual base.

And in this case, the author of the new class is simply saying "I'll have what
he's having" (the direct base class that includes the virtual base). I
understand that this technique would have been very useful to force a unique
identifier per class in the hierarchy, but now we're allowing a derived class
not to override the identifier (or silently forget to).

Do we need a keyword to specifically allow this new functionality?

> It might also be the case
> that that value is an invariant for the hierarchy, although I'm not
> sure whether anyone writes such hierarchies.
> Practically, that means passing 'this' up the hierarchy into the virtual
> base.


Ville Voutilainen

unread,
Mar 19, 2017, 4:01:54 PM3/19/17
to ISO C++ Standard - Future Proposals
On 19 March 2017 at 21:10, Thiago Macieira <thi...@macieira.org> wrote:
> On domingo, 19 de março de 2017 11:32:04 PDT Ville Voutilainen wrote:
>> On 19 March 2017 at 20:20, Thiago Macieira <thi...@macieira.org> wrote:
>> >> Right, so this proposal doesn't produce UB. That leaves the other
>> >> compatibility issues.
>> >
>> > Which ones?
>>
>> See a couple of emails ago. The cases where the most-derived class
>> doesn't have access to the constructor
>> of the virtual base; that makes intentionally ill-formed cases well-formed.
>
> Right. This may have been used in existing code to limit inheritance in a tree
> to only classes that are explicitly white-listed as friends at some point.
>
> I'm wondering if now allowing this inheritance is a problem.

The technique appears in http://www.stroustrup.com/bs_faq2.html#no-derivation

> And in this case, the author of the new class is simply saying "I'll have what
> he's having" (the direct base class that includes the virtual base). I
> understand that this technique would have been very useful to force a unique
> identifier per class in the hierarchy, but now we're allowing a derived class
> not to override the identifier (or silently forget to).
>
> Do we need a keyword to specifically allow this new functionality?

Grr. Let's not make it that complex. I think another interesting case is this:

#include <iostream>

struct B
{
B() {std::cout << "B default" << std::endl;}
B(int) {std::cout << "B int" << std::endl;}
};

struct D : virtual B
{
D() : B(42) {}
};

struct E : D
{
};

int main()
{
E e;
}

We presumably don't want to change how that behaves. So, in some
cases, E does initialize the virtual
base, in others, it doesn't. So, trying to keep that unchanged and the
no-derivation case working, we have something like

1) if default-initializing B from E is valid, we're done.
2) if default-initializing B from E fails due to access violation, we're done.
3) if default-initializing B from E fails due to overload resolution
failure for not finding a viable candidate,
don't initialize B in E but instead initialize E in D.
4) any existing explicit initialization of B from E is unchanged, and
there's no fallback to any base class.

Thiago Macieira

unread,
Mar 19, 2017, 8:36:26 PM3/19/17
to std-pr...@isocpp.org
On domingo, 19 de março de 2017 13:01:52 PDT Ville Voutilainen wrote:
> 1) if default-initializing B from E is valid, we're done.
> 2) if default-initializing B from E fails due to access violation, we're
> done. 3) if default-initializing B from E fails due to overload resolution
> failure for not finding a viable candidate,
> don't initialize B in E but instead initialize E in D.
> 4) any existing explicit initialization of B from E is unchanged, and
> there's no fallback to any base class.

You're excluding the "I'll have what he's having" case. That is, in your
example, there's no way to have E initilise B like D does without specifically
duplicating what D does.

Ville Voutilainen

unread,
Mar 19, 2017, 8:57:18 PM3/19/17
to ISO C++ Standard - Future Proposals
On 20 March 2017 at 02:36, Thiago Macieira <thi...@macieira.org> wrote:
> On domingo, 19 de março de 2017 13:01:52 PDT Ville Voutilainen wrote:
>> 1) if default-initializing B from E is valid, we're done.
>> 2) if default-initializing B from E fails due to access violation, we're
>> done. 3) if default-initializing B from E fails due to overload resolution
>> failure for not finding a viable candidate,
>> don't initialize B in E but instead initialize E in D.
>> 4) any existing explicit initialization of B from E is unchanged, and
>> there's no fallback to any base class.
>
> You're excluding the "I'll have what he's having" case. That is, in your
> example, there's no way to have E initilise B like D does without specifically
> duplicating what D does.

Well, if B doesn't have a default constructor, don't write any
initialization for B in E.
Then you get what D gets, unless there's an access violation in the
potential initialization
of B in E. So no, I'm not trying to provide an additional facility
that always gives E
what D gets.
Reply all
Reply to author
Forward
0 new messages