Using this in initialization list??

Discussion in 'C++' started by huili80@gmail.com, May 6, 2009.

  1. Guest

    Is the following code valid?


    template < typename F >
    struct base1
    {
    base1(const F& f):f(f){}
    const F& f;
    };

    template < typename E1, typename E2 >
    struct base2
    {
    base2(const E1& e1, const E2& e2):e1(e1),e2(e2){}
    const E1& e1;
    const E2& e2;
    };

    template < typename F1, typename F2 >
    struct derived : base1<F1>
    ,base2<base1<F1>,F2>
    {
    derived(const F1& f1, const F2& f2)
    :base1<F1>(f1)
    ,base2<base1<F1>,F2>(static_cast<base1<F1> const&>(*this), f2) // is
    this okay?
    {}
    };
    , May 6, 2009
    #1
    1. Advertising

  2. Neelesh Guest

    On May 6, 5:36 am, wrote:
    > Is the following code valid?
    >
    > template < typename F >
    > struct base1
    > {
    >         base1(const F& f):f(f){}
    >         const F& f;
    >
    > };
    >
    > template < typename E1, typename E2 >
    > struct base2
    > {
    >         base2(const E1& e1, const E2& e2):e1(e1),e2(e2){}
    >         const E1& e1;
    >         const E2& e2;
    >
    > };
    >
    > template < typename F1, typename F2 >
    > struct derived : base1<F1>
    >                                 ,base2<base1<F1>,F2>
    > {
    >         derived(const F1& f1, const F2& f2)
    >                 :base1<F1>(f1)
    >                 ,base2<base1<F1>,F2>(static_cast<base1<F1> const&>(*this), f2) // is
    > this okay?
    >         {}
    >
    >
    >
    > };


    Yes, the code is valid and the static_cast is not needed.
    12.6.2p7
    ....because the meminitializer are evaluated in the scope of the
    constructor, the this pointer can be used in the expressionlist of a
    meminitializer to refer to the object being initialized.
    Neelesh, May 6, 2009
    #2
    1. Advertising

  3. James Kanze Guest

    On May 6, 6:22 am, Neelesh <> wrote:
    > On May 6, 5:36 am, wrote:
    > > Is the following code valid?


    > > template < typename F >
    > > struct base1
    > > {
    > > base1(const F& f):f(f){}
    > > const F& f;
    > > };


    > > template < typename E1, typename E2 >
    > > struct base2
    > > {
    > > base2(const E1& e1, const E2& e2):e1(e1),e2(e2){}
    > > const E1& e1;
    > > const E2& e2;
    > > };


    > > template < typename F1, typename F2 >
    > > struct derived : base1<F1>
    > > ,base2<base1<F1>,F2>
    > > {
    > > derived(const F1& f1, const F2& f2)
    > > :base1<F1>(f1)
    > > ,base2<base1<F1>,F2>(static_cast<base1<F1> const&>(*this), f2) // is
    > > this okay?
    > > {}
    > > };


    > Yes, the code is valid and the static_cast is not needed.
    > 12.6.2p7
    > ...because the meminitializer are evaluated in the scope of the
    > constructor, the this pointer can be used in the expressionlist of a
    > meminitializer to refer to the object being initialized.


    No, it's not valid. The expression *this is an lvalue
    expression with type derived. Type derived has a non-trivial
    constructor. According to §3.8:

    [...] The lifetime of an object of type T begins when:

    -- storage with the proper alignment and size for type
    T is obtained, and

    -- if T is a class type with a non-trivial constructor
    (12.1), the constructor call has completed.

    Furthermore:

    Before the lifetime of an object has started but after
    the storage which the object will occupy has been
    allocated or, after the lifetime of an object has ended
    and before the storage which the object occupied is
    reused or released, any pointer that refers to the
    storage location where the object will be or was located
    may be used but only in limited ways. Such a pointer
    refers to allocated storage (3.7.3.2), and using the
    pointer as if the pointer were of type void*, is
    well-defined. Such a pointer may be dereferenced but the
    resulting lvalue may only be used in limited ways, as
    described below. If the object will be or was of a class
    type with a non-trivial destructor, and the pointer is
    used as the operand of a delete-expression, the program
    has undefined behavior. If the object will be or was of
    a non-POD class type, the program has undefined behavior
    if:

    [...]

    -- the pointer is used to access a non-static data
    member or call a non-static member function of the
    object, or

    -- the pointer is implicitly converted (4.10) to a
    pointer to a base class type, or

    -- the pointer is used as the operand of a static_cast
    (5.2.9) (except when the conversion is to void*, or
    to void* and subsequently to char*, or unsigned
    char*).

    There's no exception that I can see for the this pointer.

    Practically, I'm not sure what this means. You definitely
    can call non-static member functions of already constructed
    sub-objects, and you obviously call the constructor of the
    sub-objects, so the compiler must be able to convert the
    this pointer at this point. On the other hand, I've had
    such conversions actually fail (admittedly only when the
    base class in question was a virtual base); in my case,
    adding a member function in the base class which did nothing
    but return this, and calling it, solved the problem.
    (Similarly, I've never heard of it failing in the body of
    the constructor, although according to the above text, it's
    still undefined behavior.)

    --
    James Kanze (GABI Software) email:
    Conseils en informatique orientée objet/
    Beratung in objektorientierter Datenverarbeitung
    9 place Sémard, 78210 St.-Cyr-l'École, France, +33 (0)1 30 23 00 34
    James Kanze, May 6, 2009
    #3
  4. Neelesh Guest

    On May 6, 1:51 pm, James Kanze <> wrote:
    > On May 6, 6:22 am, Neelesh <> wrote:
    >
    >
    >
    >
    >
    > > On May 6, 5:36 am, wrote:
    > > > Is the following code valid?
    > > > template < typename F >
    > > > struct base1
    > > > {
    > > >         base1(const F& f):f(f){}
    > > >         const F& f;
    > > > };
    > > > template < typename E1, typename E2 >
    > > > struct base2
    > > > {
    > > >         base2(const E1& e1, const E2& e2):e1(e1),e2(e2){}
    > > >         const E1& e1;
    > > >         const E2& e2;
    > > > };
    > > > template < typename F1, typename F2 >
    > > > struct derived : base1<F1>
    > > >                                 ,base2<base1<F1>,F2>
    > > > {
    > > >         derived(const F1& f1, const F2& f2)
    > > >                 :base1<F1>(f1)
    > > >                 ,base2<base1<F1>,F2>(static_cast<base1<F1> const&>(*this), f2) // is
    > > > this okay?
    > > >         {}
    > > > };

    > > Yes, the code is valid and the static_cast is not needed.
    > > 12.6.2p7
    > > ...because the meminitializer are evaluated in the scope of the
    > > constructor, the this pointer can be used in the expressionlist of a
    > > meminitializer to refer to the object being initialized.

    >
    > No, it's not valid.  The expression *this is an lvalue
    > expression with type derived.  Type derived has a non-trivial
    > constructor.  According to §3.8:
    >
    >     [...] The lifetime of an object of type T begins when:
    >
    >      -- storage with the proper alignment and size for type
    >         T is obtained, and
    >
    >      -- if T is a class type with a non-trivial constructor
    >         (12.1), the constructor call has completed.
    >
    > Furthermore:
    >
    >     Before the lifetime of an object has started but after
    >     the storage which the object will occupy has been
    >     allocated or, after the lifetime of an object has ended
    >     and before the storage which the object occupied is
    >     reused or released, any pointer that refers to the
    >     storage location where the object will be or was located
    >     may be used but only in limited ways.  Such a pointer
    >     refers to allocated storage (3.7.3.2), and using the
    >     pointer as if the pointer were of type void*, is
    >     well-defined. Such a pointer may be dereferenced but the
    >     resulting lvalue may only be used in limited ways, as
    >     described below. If the object will be or was of a class
    >     type with a non-trivial destructor, and the pointer is
    >     used as the operand of a delete-expression, the program
    >     has undefined behavior. If the object will be or was of
    >     a non-POD class type, the program has undefined behavior
    >     if:
    >
    >     [...]
    >
    >      -- the pointer is used to access a non-static data
    >         member or call a non-static member function of the
    >         object, or
    >
    >      -- the pointer is implicitly converted (4.10) to a
    >         pointer to a base class type, or
    >
    >      -- the pointer is used as the operand of a static_cast
    >         (5.2.9) (except when the conversion is to void*, or
    >         to void* and subsequently to char*, or unsigned
    >         char*).
    >
    > There's no exception that I can see for the this pointer.
    >


    hmm...yes..agreed. In fact, in the current case, 3.8p6 applies more
    appropriately:

    if the original object will be or was of a nonPOD class type, the
    program has undefined behavior if:
    [...]
    — the lvalue is implicitly converted (4.10) to a reference to a base
    class type, or
    [...]

    The lvalue (*this) has been converted to reference to base class type
    (base<F1>&) in the constructor of derived class, which would result in
    undefined behavior.

    However, I believe that this is somewhat strange and I donot see any
    obvious reason of not allowing this.

    > Practically, I'm not sure what this means.  You definitely
    > can call non-static member functions of already constructed
    > sub-objects,


    calling virtual functions of already constructed sub-objects could be
    a problem in a sense that it might behave differently for *this and
    for any other object of the same type. In the current case, if we use
    (*this) to initialize a reference e1 in constructor of base2, and call
    a virtual member function of e1 from constructor of class base2, I
    would not be surprized if the call is resolved by the base1 version,
    since there is no derived object constructed yet.

    template < typename F > struct base1
    {
    base1(const F& f): f(f) { }
    const F& f;
    virtual void fun() const { cout << "base fun" << endl; }

    };

    template < typename E1, typename E2 > struct base2
    {
    base2(const E1& e1, const E2& e2):
    e1(e1),
    e2(e2)
    {
    e1.fun(); //calling member functions of member objects is fine in
    general, but creates problems when e1 is a reference to the derived
    object being constructed
    }
    const E1& e1;
    const E2& e2;

    };

    template < typename F1, typename F2 > struct derived : base1<F1>,
    base2<base1<F1>,F2>
    {
    derived(const F1& f1, const F2& f2):
    base1<F1>(f1),
    base2<base1<F1>,F2>(*this, f2) //Assume this was legal somehow
    {}

    virtual void fun() const { cout << "derived fun" << endl; }

    };

    The call e1.fun(); would typically get resolved dynamically. However,
    in the current case, since we are passing a reference to an not-yet-
    completely constructed-object, the call would get resolved by calling
    the base1::fun() instead of derived::fun(). (Of course provided that
    passing *this in the constructor of derived class was somehow legal.)

    Another example to illustrate this point:

    class X
    {
    public:
    X() { }
    X(const X& x1, const X& x2)
    {
    x1.f(); x2.f(); //check if call to f() is resolved dynamically or
    not
    }
    virtual void f() const { std::cout << "base" << std::endl; }
    };

    class Z :public X
    {

    public:
    Z() { }
    Z(int m, const X& x) : X(x, *this) //pass two objects of same
    type, one completely constructed and one partially constructed. Assume
    that passing *this was valid somehow.
    { }

    virtual void f() const { std::cout << "derived" << std::endl; }
    };


    int main()
    {
    Z z;
    Z z2(1, z); //Dummy parameter 1 added, otherwise it will call the
    CC.
    }


    Prints "base" and the "derived", indicating that x.f() is resolved
    dynamically but not x2.f().

    However, I doubt if this is "strong" enough reason to disallow passing
    (*this) to base class constructor.

    Thanks.

    >[...]
    Neelesh, May 6, 2009
    #4
  5. James Kanze Guest

    On May 6, 1:07 pm, Neelesh <> wrote:
    > On May 6, 1:51 pm, James Kanze <> wrote:
    > > On May 6, 6:22 am, Neelesh <> wrote:


    > > > On May 6, 5:36 am, wrote:
    > > > > Is the following code valid?
    > > > > template < typename F >
    > > > > struct base1
    > > > > {
    > > > > base1(const F& f):f(f){}
    > > > > const F& f;
    > > > > };
    > > > > template < typename E1, typename E2 >
    > > > > struct base2
    > > > > {
    > > > > base2(const E1& e1, const E2& e2):e1(e1),e2(e2){}
    > > > > const E1& e1;
    > > > > const E2& e2;
    > > > > };
    > > > > template < typename F1, typename F2 >
    > > > > struct derived : base1<F1>
    > > > > ,base2<base1<F1>,F2>
    > > > > {
    > > > > derived(const F1& f1, const F2& f2)
    > > > > :base1<F1>(f1)
    > > > > ,base2<base1<F1>,F2>(static_cast<base1<F1> const&>(*this), f2) // is
    > > > > this okay?
    > > > > {}
    > > > > };
    > > > Yes, the code is valid and the static_cast is not needed.
    > > > 12.6.2p7
    > > > ...because the meminitializer are evaluated in the scope
    > > > of the constructor, the this pointer can be used in the
    > > > expressionlist of a meminitializer to refer to the object
    > > > being initialized.


    > > No, it's not valid. The expression *this is an lvalue
    > > expression with type derived. Type derived has a
    > > non-trivial constructor. According to §3.8:


    > > [...] The lifetime of an object of type T begins when:


    > > -- storage with the proper alignment and size for type
    > > T is obtained, and


    > > -- if T is a class type with a non-trivial constructor
    > > (12.1), the constructor call has completed.


    > > Furthermore:


    > > Before the lifetime of an object has started but after
    > > the storage which the object will occupy has been
    > > allocated or, after the lifetime of an object has ended
    > > and before the storage which the object occupied is
    > > reused or released, any pointer that refers to the
    > > storage location where the object will be or was located
    > > may be used but only in limited ways. Such a pointer
    > > refers to allocated storage (3.7.3.2), and using the
    > > pointer as if the pointer were of type void*, is
    > > well-defined. Such a pointer may be dereferenced but the
    > > resulting lvalue may only be used in limited ways, as
    > > described below. If the object will be or was of a class
    > > type with a non-trivial destructor, and the pointer is
    > > used as the operand of a delete-expression, the program
    > > has undefined behavior. If the object will be or was of
    > > a non-POD class type, the program has undefined behavior
    > > if:


    > > [...]


    > > -- the pointer is used to access a non-static data
    > > member or call a non-static member function of the
    > > object, or


    > > -- the pointer is implicitly converted (4.10) to a
    > > pointer to a base class type, or


    > > -- the pointer is used as the operand of a static_cast
    > > (5.2.9) (except when the conversion is to void*, or
    > > to void* and subsequently to char*, or unsigned
    > > char*).


    > > There's no exception that I can see for the this pointer.


    > hmm...yes..agreed. In fact, in the current case, 3.8p6 applies
    > more appropriately:


    > if the original object will be or was of a nonPOD class type, the
    > program has undefined behavior if:
    > [...]
    > — the lvalue is implicitly converted (4.10) to a reference to a base
    > class type, or
    > [...]


    > The lvalue (*this) has been converted to reference to base
    > class type (base<F1>&) in the constructor of derived class,
    > which would result in undefined behavior.


    > However, I believe that this is somewhat strange and I donot
    > see any obvious reason of not allowing this.


    I can't either. I think the case the standard is trying to
    address is that of some external pointer. Something along the
    lines of:

    Derived* p = static_cast< Derived* >(
    ::eek:perator new( sizeof Derived ) ) ;
    new ( p ) Derived ;

    and then the base class constructor calls a function which tries
    to convert p to a Base*. There are very good reasons why this
    should be undefined behavior. And those reasons really apply to
    just about any pointer except this; the compiler can only do the
    conversion correctly if it knows (more or less) how much has or
    has not been constructed (at least if the base is virtual). In
    the case of this, however, the compiler knows exactly what the
    situation is, what parts have been constructed, and what parts
    haven't. And the compiler has to be able to do the conversion
    in order to call the various constructors of the base classes
    anyway, or to call member functions in already constructed base
    classes (which is allowed).

    > > Practically, I'm not sure what this means. You definitely
    > > can call non-static member functions of already constructed
    > > sub-objects,


    > calling virtual functions of already constructed sub-objects
    > could be a problem in a sense that it might behave differently
    > for *this and for any other object of the same type.


    Agreed. I'm not sure what calling a virtual function in such
    cases would mean. I.e. given:

    Derived::Derived() : Base1(), Base2( virtualFuncInBase1() ) {}

    When "virtualFuncInBase1" is called, what is the dynamic type of
    the object? It can't yet be Derived, since we've yet to enter
    the body of Derived, and in the constructor of Base2, it will be
    Base2. And if it is Base2, there's likely no virtualFuncInBase1
    in Base2, so the code shouldn't even compile. Maybe such calls
    are illegal. (My Unix machine is in the process of being
    upgraded, and all my copies of the standard are on it, so I
    can't verify anything for the moment.) What I can say is that
    I've actually had a case a bit like the above, where Base1 was
    virtual, and Base2 took a pointer to a base class of Base1. If I
    passed this, the generated code core dumped; if I called a
    member function of Base1 which returned this, the code worked.
    But of course, the behavior of one compiler doesn't prove
    anything (especially as other compilers got it right when I
    simply passed this).

    I'll look into it further once I get access to the standard
    again. The case does occur in one very common idiom: when
    creating an istream or ostream to use a custom streambuf. The
    classic idiom is:

    class myistream : private mystreambuf, public std::istream
    {
    public:
    myistream() : mystreambuf(), std::istream( this ) {}
    } ;

    , with variations on whether you explicitly cast the this or
    not, whether the inhertance of mystreambuf is virtual or not,
    etc. In the end, what I currently have is a wrapper class
    (templated) which contains the mystreambuf, and has a function
    which returns a pointer to it. The initializer expression for
    std::istream calls this function.

    > In the current case, if we use (*this) to initialize a
    > reference e1 in constructor of base2, and call a virtual
    > member function of e1 from constructor of class base2, I would
    > not be surprized if the call is resolved by the base1 version,
    > since there is no derived object constructed yet.


    That would be correct (supposing that Base2 takes a reference to
    a Base1). I don't see any problem there, provided the correct
    pointer (to the Base1 subobject) is passed to the constructor of
    Base2. The problem is getting the correct pointer. Consider:

    Derived* current ;

    Derived* factory()
    {
    current = static_cast< Derived* >(
    ::eek:perator new( sizeof( Derived ) ) ;
    new ( current ) Derived ;
    return current ;
    }

    Derived::Derived()
    : Base1()
    , Base2( *current ) // expects a Base1&
    {
    }

    If Base1 is virtual, do you really expect a compiler to be able
    to generate the correct code for the call to Base2 (supposing
    that it doesn't know analyse enought to know that current is
    always equal to this at this point---and I doubt many compilers
    are capable of that analysis). It can only suppose that current
    points to a fully constructed object, with a vptr which
    corresponds to that of the full object. Which isn't necessarily
    the case.

    The difference when you pass the this pointer, of course, is
    that the compiler does know the exact state of the pointed to
    object. And it does know how to find the address of any given
    base class. (It has to, since it has to be able to set up the
    this pointer when calling member functions of Base1 from the
    body of the constructor.)

    > template < typename F > struct base1


    Just a nit, but none of this has anything to do with templates.
    Using templates in the examples just adds extra noise for the
    reader.

    > {
    > base1(const F& f): f(f) { }
    > const F& f;
    > virtual void fun() const { cout << "base fun" << endl; }
    > };


    > template < typename E1, typename E2 > struct base2
    > {
    > base2(const E1& e1, const E2& e2):
    > e1(e1),
    > e2(e2)
    > {
    > e1.fun(); //calling member functions of member objects is fine in
    > general, but creates problems when e1 is a reference to the derived
    > object being constructed


    If E1 is truely the derived type, the code has undefined
    behavior. If E1 is an already constructed base class of the
    derived type, the dynamic type (to which the virtual function
    resolves) of e1 should be that of the already constructed base
    class. Undefined behavior if the function is pure virtual in
    this class; well defined behavior (but not necessarily what is
    wanted) otherwise.

    I don't think that this is really any different than calling any
    other function which calls a virtual function in the already
    constructed base. The problem in question is getting the
    correct address for e1.

    > }
    > const E1& e1;
    > const E2& e2;
    > };


    > template < typename F1, typename F2 > struct derived : base1<F1>,
    > base2<base1<F1>,F2>
    > {
    > derived(const F1& f1, const F2& f2):
    > base1<F1>(f1),
    > base2<base1<F1>,F2>(*this, f2) //Assume this was legal somehow
    > {}


    > virtual void fun() const { cout << "derived fun" << endl; }
    > };


    > The call e1.fun(); would typically get resolved dynamically.
    > However, in the current case, since we are passing a
    > reference to an not-yet- completely constructed-object, the
    > call would get resolved by calling the base1::fun() instead of
    > derived::fun(). (Of course provided that passing *this in the
    > constructor of derived class was somehow legal.)


    Yes. This is well known behavior; it crops up all the time. It
    surprises newcomers, but after a bit of analysis, it is clear
    that all of the alternative solutions are worse.

    > Another example to illustrate this point:


    > class X
    > {
    > public:
    > X() { }
    > X(const X& x1, const X& x2)
    > {
    > x1.f(); x2.f(); //check if call to f() is resolved dynamically or
    > not
    > }
    > virtual void f() const { std::cout << "base" << std::endl; }
    > };


    > class Z :public X
    > {
    >
    > public:
    > Z() { }
    > Z(int m, const X& x) : X(x, *this) //pass two objects of same
    > type, one completely constructed and one partially constructed. Assume
    > that passing *this was valid somehow.
    > { }
    > virtual void f() const { std::cout << "derived" << std::endl; }
    > };


    > int main()
    > {
    > Z z;
    > Z z2(1, z); //Dummy parameter 1 added, otherwise it will call the
    > CC.
    > }


    > Prints "base" and the "derived", indicating that x.f() is
    > resolved dynamically but not x2.f().


    Both are resolved dynamically. The difference is that when
    executing X::X(), the dynamic type of the object is X.

    > However, I doubt if this is "strong" enough reason to disallow
    > passing (*this) to base class constructor.


    I don't think that this has anything to do with it. The same
    "problem" occurs no matter how you call f() in X::X(). The
    problem is that in some cases, the compiler needs
    meta-information (from the vtbl or elsewhere in the constructed
    class) is order to do the derived to base conversion. This
    information isn't necessarily valid until you've actually
    entered the body of the constructor of the most derived class.
    The case of this is different, because 1) the compiler knows
    that the class is in the process of being constructed, and is
    not in its final state, and 2) the compiler must be able to make
    the necessary conversion anyway.

    --
    James Kanze (GABI Software) email:
    Conseils en informatique orientée objet/
    Beratung in objektorientierter Datenverarbeitung
    9 place Sémard, 78210 St.-Cyr-l'École, France, +33 (0)1 30 23 00 34
    James Kanze, May 7, 2009
    #5
  6. * James Kanze:
    > On May 6, 1:07 pm, Neelesh <> wrote:
    >> On May 6, 1:51 pm, James Kanze <> wrote:
    >>> On May 6, 6:22 am, Neelesh <> wrote:

    >
    >>>> On May 6, 5:36 am, wrote:
    >>>>> Is the following code valid?
    >>>>> template < typename F >
    >>>>> struct base1
    >>>>> {
    >>>>> base1(const F& f):f(f){}
    >>>>> const F& f;
    >>>>> };
    >>>>> template < typename E1, typename E2 >
    >>>>> struct base2
    >>>>> {
    >>>>> base2(const E1& e1, const E2& e2):e1(e1),e2(e2){}
    >>>>> const E1& e1;
    >>>>> const E2& e2;
    >>>>> };
    >>>>> template < typename F1, typename F2 >
    >>>>> struct derived : base1<F1>
    >>>>> ,base2<base1<F1>,F2>
    >>>>> {
    >>>>> derived(const F1& f1, const F2& f2)
    >>>>> :base1<F1>(f1)
    >>>>> ,base2<base1<F1>,F2>(static_cast<base1<F1> const&>(*this), f2) // is
    >>>>> this okay?
    >>>>> {}
    >>>>> };
    >>>> Yes, the code is valid and the static_cast is not needed.
    >>>> 12.6.2p7
    >>>> ...because the meminitializer are evaluated in the scope
    >>>> of the constructor, the this pointer can be used in the
    >>>> expressionlist of a meminitializer to refer to the object
    >>>> being initialized.

    >
    >>> No, it's not valid. The expression *this is an lvalue
    >>> expression with type derived. Type derived has a
    >>> non-trivial constructor. According to §3.8:

    >
    >>> [...] The lifetime of an object of type T begins when:

    >
    >>> -- storage with the proper alignment and size for type
    >>> T is obtained, and

    >
    >>> -- if T is a class type with a non-trivial constructor
    >>> (12.1), the constructor call has completed.

    >
    >>> Furthermore:

    >
    >>> Before the lifetime of an object has started but after
    >>> the storage which the object will occupy has been
    >>> allocated or, after the lifetime of an object has ended
    >>> and before the storage which the object occupied is
    >>> reused or released, any pointer that refers to the
    >>> storage location where the object will be or was located
    >>> may be used but only in limited ways. Such a pointer
    >>> refers to allocated storage (3.7.3.2), and using the
    >>> pointer as if the pointer were of type void*, is
    >>> well-defined. Such a pointer may be dereferenced but the
    >>> resulting lvalue may only be used in limited ways, as
    >>> described below. If the object will be or was of a class
    >>> type with a non-trivial destructor, and the pointer is
    >>> used as the operand of a delete-expression, the program
    >>> has undefined behavior. If the object will be or was of
    >>> a non-POD class type, the program has undefined behavior
    >>> if:

    >
    >>> [...]

    >
    >>> -- the pointer is used to access a non-static data
    >>> member or call a non-static member function of the
    >>> object, or

    >
    >>> -- the pointer is implicitly converted (4.10) to a
    >>> pointer to a base class type, or

    >
    >>> -- the pointer is used as the operand of a static_cast
    >>> (5.2.9) (except when the conversion is to void*, or
    >>> to void* and subsequently to char*, or unsigned
    >>> char*).

    >
    >>> There's no exception that I can see for the this pointer.

    >
    >> hmm...yes..agreed. In fact, in the current case, 3.8p6 applies
    >> more appropriately:

    >
    >> if the original object will be or was of a nonPOD class type, the
    >> program has undefined behavior if:
    >> [...]
    >> — the lvalue is implicitly converted (4.10) to a reference to a base
    >> class type, or
    >> [...]

    >
    >> The lvalue (*this) has been converted to reference to base
    >> class type (base<F1>&) in the constructor of derived class,
    >> which would result in undefined behavior.

    >
    >> However, I believe that this is somewhat strange and I donot
    >> see any obvious reason of not allowing this.

    >
    > I can't either. I think the case the standard is trying to
    > address is that of some external pointer. Something along the
    > lines of:
    >
    > Derived* p = static_cast< Derived* >(
    > ::eek:perator new( sizeof Derived ) ) ;
    > new ( p ) Derived ;
    >
    > and then the base class constructor calls a function which tries
    > to convert p to a Base*. There are very good reasons why this
    > should be undefined behavior. And those reasons really apply to
    > just about any pointer except this; the compiler can only do the
    > conversion correctly if it knows (more or less) how much has or
    > has not been constructed (at least if the base is virtual).


    AFAICS the compiler doesn't need to know how much has been constructed, but it
    does need to know location of the base class subobject, which can be a run-time
    value in the case of a virtual base.

    This location must be known at the latest when execution of the Derived
    constructor's body starts, because there Base might be referred to.

    So what it boils down to is whether a C++ compiler is permitted to allocate a
    virtual base subobject dynamically, in which case the location might not be
    known until it has been constructed.

    I think the base class subobject cannot be dynamically allocated, for reasons
    that I have partially forgotten but which were convincing to me at the time I
    analysed this, but as I recall Dave Abrahams thought yes, it can be. Anyway, it
    seems it's the same situation as with assuming that std::string has a contiguous
    buffer. Even though the current standard doesn't guarantee that, one would be
    Really Hard Pressed to find an extant compiler where std::string has a
    non-contiguous buffer, and likewise, one would be Really Hard Pressed to find a
    compiler where a virtual base class object is allocated dynamically...

    And when it's not allocated dynamically, as one can assume for the in-practice,
    then the compiler has all the information that it needs for the cast --
    namely, the offset of the Base subobject relative to any particular Derived
    object -- at compile time. And it needs to pass that offset so that it's known
    when execution of the Derived constructor's body starts. So there's then no
    reason why it can't be available also when the mem initializer list is executed,
    even for routines called from the mem initializer list, because all the compiler
    needs to do is to store it wherever general code assumes that it is stored.

    And so it seems that the standard really should make an exception for the case
    of up-conversion from Derived of this or *this after execution of a Derived
    constructor's mem-initializer list has begun.

    For it's common practice, and it's counter-productive to have compilers warn
    about it or (as some future compiler might do) outright reject it.

    And if that turns out to be difficult to get that idea through the politics,
    then IMHO at least an exception for Derived with no virtual bases.

    For in that case, as I recall, there's really no problem that isn't already
    addressed (e.g. one problem is dereferencing a pointer passed elsewhere before
    relevant part has been constructed, and as I recall that's already addressed).


    Cheers,

    - Alf

    --
    Due to hosting requirements I need visits to <url: http://alfps.izfree.com/>.
    No ads, and there is some C++ stuff! :) Just going there is good. Linking
    to it is even better! Thanks in advance!
    Alf P. Steinbach, May 7, 2009
    #6
    1. Advertising

Want to reply to this thread or ask your own question?

It takes just 2 minutes to sign up (and it's free!). Just click the sign up button to choose a username and then you can ask your own questions on the forum.
Similar Threads
  1. JKop
    Replies:
    10
    Views:
    924
  2. Matthias Kaeppler
    Replies:
    2
    Views:
    429
    Victor Bazarov
    Jul 18, 2005
  3. Replies:
    6
    Views:
    451
    Ron Natalie
    Dec 11, 2005
  4. toton
    Replies:
    5
    Views:
    920
    Victor Bazarov
    Sep 28, 2006
  5. aaragon
    Replies:
    2
    Views:
    606
    James Kanze
    Nov 2, 2008
Loading...

Share This Page