Using "this" in a base initializer list?

A

Angel Tsankov

Hello! Is the following code illformed or does it yield undefined
behaviour:

class a

{};

class b

{

public:

b(a&){}

};

class c: private a, private b

{

public:

c():b(*this){}

};
 
V

Victor Bazarov

Angel said:
Hello! Is the following code illformed or does it yield undefined
behaviour:

class a

{};

class b

{

public:

b(a&){}

};

class c: private a, private b

{

public:

c():b(*this){}

};

Neither. The code is fine.

V
 
S

siddhu

Hello! Is the following code illformed or does it yield undefined
behaviour:

class a

{};

class b

{

public:

b(a&){}

};

class c: private a, private b

{

public:

c():b(*this){}

Ambiguous call. b(const b&) and b(a&)
 
J

James Kanze

Neither. The code is fine.

Are you sure? Throw in some virtual inheritance, and it core
dumps with Sun CC. And §3.8 of the standard explicitly says
that:

Before the lifetime of an object has started but after
the storage which the object will occupy has been
allocated39) 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.[...] If the
object will be or was of a non-POD class type, the
program has undefined behavior if
[...]
-- the pointer is implicitly converted (4.10) to a
pointer to a base class type,

(Quoted from the April, 2006 draft. The only one I have
handy here. But I don't think the text in question has
changed since the original standard.)

I think that there are some special rules concerning the
this pointer, but I can't find them off hand. If you can
find them, and point them out to me, I'd be very happy.
I really want this to be defined.
 
A

Angel Tsankov

--
Angel Tsankov
(e-mail address removed)-sofia.bg
siddhu said:
Ambiguous call. b(const b&) and b(a&)

Well, I meant this:

class c: private a, private b
{
public:
c():b(static_cast<a&>(*this)){}
};
 
V

Victor Bazarov

James said:
Neither. The code is fine.

Are you sure? Throw in some virtual inheritance, and it core
dumps with Sun CC. [..]

Huh? Throw in division by zero or dereferencing a null pointer
and it may release nasal demons. What's it got to do with the
code at hand?
 
A

Alf P. Steinbach

* Victor Bazarov:
James said:
Angel Tsankov wrote:
Hello! Is the following code illformed or does it yield undefined
behaviour:
class a
{};
class b
{
public:
b(a&){}
};
class c: private a, private b
{
public:
c():b(*this){}
};
Neither. The code is fine.
Are you sure? Throw in some virtual inheritance, and it core
dumps with Sun CC. [..]

Huh? Throw in division by zero or dereferencing a null pointer
and it may release nasal demons. What's it got to do with the
code at hand?

James is referring to the conversion from c* to a*, which §3.8/5 says is
Undefined Behavior. But the interpretation that yields that conclusion
also means that this,

struct S
{
void foo() {}
S() { this->foo(); }
};

is undefined behavior, so that the common technique of using an
init-function called from multiple constructors, would be formally
Undefined Behavior. Which really means that part of the standard is
fishy Fishy FISHY, and should not be taken verbatim: some constructive
interpretation required, and I think it's impossible to say whether the
code above is formally OK (or not), only that it's in-practice OK.
 
V

Victor Bazarov

Alf said:
* Victor Bazarov:
James said:
Angel Tsankov wrote:
Hello! Is the following code illformed or does it yield undefined
behaviour:
class a
{};
class b
{
public:
b(a&){}
};
class c: private a, private b
{
public:
c():b(*this){}
};
Neither. The code is fine.
Are you sure? Throw in some virtual inheritance, and it core
dumps with Sun CC. [..]

Huh? Throw in division by zero or dereferencing a null pointer
and it may release nasal demons. What's it got to do with the
code at hand?

James is referring to the conversion from c* to a*, which §3.8/5 says
is Undefined Behavior.

Yes, but where did you find that conversion? There is reference
binding (8.5.3/5), but no pointer conversion.
But the interpretation that yields that
conclusion also means that this,

struct S
{
void foo() {}
S() { this->foo(); }
};

is undefined behavior, so that the common technique of using an
init-function called from multiple constructors, would be formally
Undefined Behavior. Which really means that part of the standard is
fishy Fishy FISHY, and should not be taken verbatim: some constructive
interpretation required, and I think it's impossible to say whether
the code above is formally OK (or not), only that it's in-practice OK.

<shrug> The reference (or pointer) is not being converted/used in the
code at all, why all the fuss?

V
 
J

James Kanze

* Victor Bazarov:
James said:
Angel Tsankov wrote:
Hello! Is the following code illformed or does it yield undefined
behaviour:
class a
{};
class b
{
public:
b(a&){}
};
class c: private a, private b
{
public:
c():b(*this){}
};
Neither. The code is fine.
Are you sure? Throw in some virtual inheritance, and it core
dumps with Sun CC. [..]
Huh? Throw in division by zero or dereferencing a null pointer
and it may release nasal demons. What's it got to do with the
code at hand?
James is referring to the conversion from c* to a*, which §3.8/5 says is
Undefined Behavior. But the interpretation that yields that conclusion
also means that this,
struct S
{
void foo() {}
S() { this->foo(); }
};
is undefined behavior, so that the common technique of using an
init-function called from multiple constructors, would be formally
Undefined Behavior. Which really means that part of the standard is
fishy Fishy FISHY,

You get that feeling too. Realistically, at least some of the
uses must be defined, regardless of what the standard says. But
which ones?
and should not be taken verbatim: some constructive
interpretation required, and I think it's impossible to say whether the
code above is formally OK (or not), only that it's in-practice OK.

In this simple case. That's why I mentionned that I'd actually
had a problem with a real compiler in the past. It's because I
had the problem that I'd familiarized myself with the standard,
too. I wanted to complain about a compiler error, but the
standard didn't support me.

Logically, I still have trouble understanding *why* it should be
undefined behavior. The compiler must be able to do the
conversion, since it has to get the correct address for the this
pointer it passes to the base class constructors. In my case,
the code was very clear:

class MyStream
: public virtual MyStreambuf
, public std::eek:stream
{
} ;

The organization was such in order to ensure that the streambuf
was constructed before its address was passed to std::eek:stream.
Initializing std::eek:stream with this, however, resulted in the
wrong address being passed, and a core dump later, when ostream
used it. Removing the virtual worked. What also worked was
wrapping the streambuf:

template< typename Streambuf >
class StreambufWrapper
{
public:
std::streambuf* getStreambuf() { return &myStreambuf ; }
Streambuf myStreambuf ;
} ;

class MyStream
: public virtual StreambufWrapper< MyStreambuf >
: public std::eek:stream
{
public:
MyStream() : std::eek:stream( getStreambuf() ) {}
} ;

Obviously, the compiler had to do successfully convert this to a
pointer to the base class in order to call getStreambuf(), but
for who knows what reason, it was unable to do it correctly when
this was passed to the constructor.
 
J

James Kanze

Alf said:
* Victor Bazarov:
James Kanze wrote:
Angel Tsankov wrote:
Hello! Is the following code illformed or does it yield undefined
behaviour:
class a
{};
class b
{
public:
b(a&){}
};
class c: private a, private b
{
public:
c():b(*this){}
};
Neither. The code is fine.
Are you sure? Throw in some virtual inheritance, and it core
dumps with Sun CC. [..]
Huh? Throw in division by zero or dereferencing a null pointer
and it may release nasal demons. What's it got to do with the
code at hand?
James is referring to the conversion from c* to a*, which §3.8/5 says
is Undefined Behavior.
Yes, but where did you find that conversion? There is reference
binding (8.5.3/5), but no pointer conversion.

So we end up in the following paragraph, which basically says
exactly the same thing about lvalues which refer to the object
(*this is an lvalue which refers to the complete C object):

Similarly, 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 lvalue which refers to the
original object may be used but only in limited
ways.[...] if the original object will be or was of a
non-POD class type, the program has undefined behavior
if:
[...]
-- the lvalue is implicitly converted (4.10) to a
reference to a base class type,

The standard rather explicitly says that this is undefined
behavior. In practice, as Alf points out, it has to work in
some cases. But we don't really know which.

It's things like this which make me think that there might be
some special rules for this. The standard does expliclty say
(§9.3.1/1) that:

A non-static member function may be called for an object
of its class type, or for an object of a class derived
(clause 10) from its class type, using the class member
access syntax (5.2.5, 13.3.1.1). A non-static member
function may also be called directly using the function
call syntax (5.2.2, 13.3.1.1)
-- from within the body of a member function of its
class or of a class derived from its class, or
-- from a mem-initializer (12.6.2) for a constructor
for its class or for a class derived from its class.

Which taken literally, means that S::foo() or just foo() is
legal, but this->foo() isn't. (Which, of course, doesn't
really make sense.)
<shrug> The reference (or pointer) is not being converted/used in the
code at all, why all the fuss?

Sorry. In the expression *this, this is a pointer to a c.
An the lvalue resulting from the expression is an lvalue
referring to the as yet not fully constructed c object. So
you have to consider such conversions. The conversion in
question is explicity defined by the standard as being
undefined behavior. In addition, as I pointed out, I have
seen the conversion, or at least similar conversions, fail
in real compilers. It bothers me, because the compiler does
have to be capable of doing the conversion, at least behind
the scenes. (But only on the this pointer, and only during
actual construction.)
 

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,744
Messages
2,569,484
Members
44,903
Latest member
orderPeak8CBDGummies

Latest Threads

Top