This is an unofficial snapshot of the ISO/IEC JTC1 SC22 WG21 Core Issues List revision 115d. See http://www.open-std.org/jtc1/sc22/wg21/ for the official list.

2024-10-26


2504. Inheriting constructors from virtual base classes

Section: 11.9.4  [class.inhctor.init]     Status: DRWP     Submitter: Hubert Tong     Date: 2021-11-03

[Accepted as a DR at the November, 2023 meeting.]

According to 11.9.4 [class.inhctor.init] paragraph 1,

When a constructor for type B is invoked to initialize an object of a different type D (that is, when the constructor was inherited (9.9 [namespace.udecl])), initialization proceeds as if a defaulted default constructor were used to initialize the D object and each base class subobject from which the constructor was inherited, except that the B subobject is initialized by the invocation of the inherited constructor. The complete initialization is considered to be a single function call; in particular, the initialization of the inherited constructor's parameters is sequenced before the initialization of any part of the Dobject.

First, this assumes that the base class constructor will be invoked from the derived class constructor, which will not be true if the base is virtual and initialized by a more-derived constructor.

If the call to the virtual base constructor is omitted, the last sentence is unclear whether the initialization of the base class constructor's parameters by the inheriting constructor occurs or not. There is implementation divergence in the initialization of V's parameter in the following example:

  struct NonTriv {
    NonTriv(int);
    ~NonTriv();
  };
  struct V { V() = default; V(NonTriv); };
  struct Q { Q(); };
  struct A : virtual V, Q {
    using V::V;
    A() : A(42) { }
  };
  struct B : A { };
  void foo() { B b; }

CWG telecon 2022-09-23:

Inheriting constructors from a virtual base class ought to be ill-formed. Inform EWG accordingly.

Possible resolution [SUPERSEDED]:

  1. Change in 9.9 [namespace.udecl] paragraph 3 as follows:

    ... If a using-declarator names a constructor, its nested-name-specifier shall name a direct non-virtual base class of the current class. If the immediate (class) scope is associated with a class template, it shall derive from the specified base class or have at least one dependent base class.
  2. Change the example in 11.9.4 [class.inhctor.init] paragraph 1 as follows:

    D2 f(1.0);  // error: B1 has a deleted no default constructor
    
    struct W { W(int); };
    struct X : virtual W { using W::W; X() = delete; };
    struct Y : X { using X::X; };
    struct Z : Y, virtual W { using Y::Y; };
    Z z(0);  // OK, initialization of Y does not invoke default constructor of X
    
  3. Change the example in 11.9.4 [class.inhctor.init] paragraph 2 as follows:

    struct V1 : virtual B { using B::B; };
    struct V2 : virtual B { using B::B; };
    
    struct D2 : V1, V2 {
      using V1::V1;
      using V2::V2;
    };
    D1 d1(0);  // error: ambiguous
    D2 d2(0);  // OK, initializes virtual B base class, which initializes the A base class
               // then initializes the V1 and V2 base classes as if by a defaulted default constructor
    

CWG telecon 2022-10-07:

Given that there are examples that discuss inheriting constructors from virtual base classes and given the existing normative wording, making it clear that NonTriv is not constructed, CWG felt that the implementation divergence is best addressed by amending the examples.

Possible resolution [SUPERSEDED]:

Add another example before 11.9.4 [class.inhctor.init] paragraph 2 as follows:

[ Example:

struct NonTriv {
  NonTriv(int);
  ~NonTriv();
};
struct V { V() = default; V(NonTriv); };
struct Q { Q(); };
struct A : virtual V, Q {
  using V::V;
  A() : A(42) { }    // #1, A(42) is equivalent to V(42)
};
struct B : A { };
void foo() { B b; }

In this example, the V subobject of b is constructed using the defaulted default constructor. The mem-initializer naming the constructor inherited from V at #1 is not evaluated and thus no object of type NonTriv is constructed. -- end example ]

If the constructor was inherited from multiple base class subobjects of type B, the program is ill-formed.

Proposed resolution (approved by CWG 2023-11-06):

  1. Change in 11.9.4 [class.inhctor.init] paragraph 1 as follows:

    When a constructor for type B is invoked to initialize an object of a different type D (that is, when the constructor was inherited (9.9 [namespace.udecl])), initialization proceeds as if a defaulted default constructor were used to initialize the D object and each base class subobject from which the constructor was inherited, except that the B subobject is initialized by the invocation of the inherited constructor if the base class subobject were to be initialized as part of the D object (11.9.3 [class.base.init]). The invocation of the inherited constructor, including the evaluation of any arguments, is omitted if the B subobject is not to be initialized as part of the D object. The complete initialization is considered to be a single function call; in particular, unless omitted, the initialization of the inherited constructor's parameters is sequenced before the initialization of any part of the Dobject.
  2. Add another example before 11.9.4 [class.inhctor.init] paragraph 2 as follows:

    [ Example:

    struct V { V() = default; V(int); };
    struct Q { Q(); };
    struct A : virtual V, Q {
      using V::V;
      A() = delete;
    };
    int bar() { return 42; }
    struct B : A {
      B() : A(bar()) {}  // ok
    };
    struct C : B {};
    void foo() { C c; } // bar is not invoked, because the V subobject is not initialized as part of B
    

    -- end example ]

CWG telecon 2022-10-21:

This is an ABI break for implementations when transitioning to the C++17 model for inheriting constructors.