0

The following example is given in the 12.7/3 N3797

struct A { };
struct B : virtual A { };
struct C : B { };
struct D : virtual A { D(A*); };
struct X { X(A*); };
struct E : C, D, X {
    E() : D(this), // undefined: upcast from E* to A*
                   // might use path E* → D* → A*
                   // but D is not constructed
                   // D((C*)this), // defined:
                   // E* → C* defined because E() has started
                   // and C* → A* defined because
                   // C fully constructed
    X(this) {      // defined: upon construction of X,
                   // C/B/D/A sublattice is fully constructed
    }
};

Rule for the example:

To explicitly or implicitly convert a pointer (a glvalue) referring to an object of class X to a pointer (reference) to a direct or indirect base class B of X, the construction of X and the construction of all of its direct or indirect bases that directly or indirectly derive from B shall have started and the destruction of these classes shall not have completed, otherwise the conversion results in undefined behavior.

But consider a bit modification of the example:

struct A { };
struct HD { }; // will be used as a base for D
struct B : virtual A { };
struct C : B { };
struct D : HD, virtual A { D(A*); };
struct X { X(A*); };
struct E : C, D, X {
    E() : D(this), // 1. Is there undefined behavior? 
                   // I think, there isn't.
    X(this) {
    }
};

I think //1 has no UB, because we have base subobject of class HD that has been constructed before the invocation of D(A*) constructor. That is, at the time D(A*) has called, the construction of D has started.

Is my reasoning correct?

Some programmer dude
  • 400,186
  • 35
  • 402
  • 621
  • 1
    I don't see how your example changes anything. First, the problem is in casting `this` (of type `E*`) to `A*` *before* `D`'s constructor is even invoked; `HD` is not constructed yet at that point. Second, even if `HD` were fully constructed, how would it help any? I don't see the point of introducing it. Why do you believe that `HD` constructor should magically jump ahead of the line? – Igor Tandetnik Aug 26 '14 at 05:09
  • @IgorTandetnik I mean that we need to cast `E*->D*->A*` at the time to `D(A*)` call, since we have that `HD` has already constructed. This implies that the entire `D` object construction already has started, therefore there is no UB. –  Aug 27 '14 at 04:28
  • We need to cast `E*->D*->A*` **before** `D(this)` call. Evaluation of function arguments is sequenced before the function is entered. In any case, what does `HD` have to do with anything? Why do you feel its presence somehow changes the picture, or alters the order in which classes are constructed? As far as I can tell, it's completely irrelevant. – Igor Tandetnik Aug 27 '14 at 05:07

1 Answers1

0

HD is not, as the OP claims, constructed before the call to D::D(A*). The order of events is that the parameter of D::D(A*) is initialized first, then D's first base class HD is constructed, then D::D(A*)'s body is entered. (D's virtual base class A was already constructed during the construction of the C base of E, so it doesn't get constructed a second time.)

Thus, the premise of the question is invalid. The OP's example has UB just like the original example from the standard; the conversion from E* to A* (required to initialize the parameter of D::D(A*)) occurs too early and causes UB.

Brian Bi
  • 111,498
  • 10
  • 176
  • 312