Using this in initialization list??

H

huili80

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?
{}
};
 
N

Neelesh

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.
 
J

James Kanze

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.)
 
N

Neelesh

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.
 
J

James Kanze

On May 6, 5:36 am, (e-mail address removed) 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).
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.
 
A

Alf P. Steinbach

* James Kanze:
On May 6, 5:36 am, (e-mail address removed) 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
 

Ask a Question

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

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,764
Messages
2,569,566
Members
45,041
Latest member
RomeoFarnh

Latest Threads

Top