Pointers to Members question

B

Ben Thomas

Hi,

Is this code okay and compliant with the standard. As far as I
understand it is correct but I just want to make sure.

Thank you,
Ben.

----

#include <iostream>
using namespace std;

class Base {
public:
typedef void (Base::*PMethod)(void);
void Invoke (PMethod p) {(this->*p)();}
};

class Derived : public Base {
public:
void Method (void) {cout << "Hello World" << endl;}
};

int main (void) {
Derived x;
// *** Is the static_cast correct here ? Or could this leads
// to undefined behavior on some platform / compiler ?
x.Invoke(static_cast<Base::pMethod>(&Derived::Method));
return 0;
}
 
V

Victor Bazarov

Ben said:
Hi,

Is this code okay and compliant with the standard. As far as I
understand it is correct but I just want to make sure.

Thank you,
Ben.

----

#include <iostream>
using namespace std;

class Base {
public:
typedef void (Base::*PMethod)(void);
void Invoke (PMethod p) {(this->*p)();}
};

class Derived : public Base {
public:
void Method (void) {cout << "Hello World" << endl;}
};

int main (void) {
Derived x;
// *** Is the static_cast correct here ? Or could this leads
// to undefined behavior on some platform / compiler ?
x.Invoke(static_cast<Base::pMethod>(&Derived::Method));
return 0;
}

Generally speaking, a member of derived is NOT a member of base,
and that's why the conversion does not exist, and you have to resort
to some tricks (like casts) to silence the compiler that complains
otherwise. The static cast (as I understand it) would be called for
if you have a pointer to member of 'Base' ('Base::*ptr'), somehow
it was converted to a pointer to a member of 'Derived' ('Derived::*')
and then your base wants to call it. Then, since _originally_ the
pointer *was* to a member of 'Base', you're ok to use 'static_cast'.
If the pointer *never was* to a member of 'Base', casting is *not*
the right thing to do. You're correct suspecting that the behaviour
is undefined.

V
 
P

Pete Becker

The static cast (as I understand it) would be called for
if you have a pointer to member of 'Base' ('Base::*ptr'), somehow
it was converted to a pointer to a member of 'Derived' ('Derived::*')
and then your base wants to call it. Then, since _originally_ the
pointer *was* to a member of 'Base', you're ok to use 'static_cast'.
If the pointer *never was* to a member of 'Base', casting is *not*
the right thing to do. You're correct suspecting that the behaviour
is undefined.

More generally, once you cast it, you have to cast it back to its
original type to get well-defined behavior.
 
J

James Kanze

Generally speaking, a member of derived is NOT a member of base,
and that's why the conversion does not exist, and you have to resort
to some tricks (like casts) to silence the compiler that complains
otherwise. The static cast (as I understand it) would be called for
if you have a pointer to member of 'Base' ('Base::*ptr'), somehow
it was converted to a pointer to a member of 'Derived' ('Derived::*')
and then your base wants to call it. Then, since _originally_ the
pointer *was* to a member of 'Base', you're ok to use 'static_cast'.
If the pointer *never was* to a member of 'Base', casting is *not*
the right thing to do. You're correct suspecting that the behaviour
is undefined.

I think you've got it backwards. Pointer to member casts work
the opposite of normal pointer casts. There is an implicit D::*
to B::* conversion (no cast needed). A static_cast is needed
for D::* to B::*, however, and the using it is only legal if the
B it is used with is actually a D. Which is the case above. So
while the above may be as ugly as sin, it is perfectly legal
(and was used for callbacks in one GUI library---XViews, I
think---that was widely used in the past).
 
V

Victor Bazarov

James said:
I think you've got it backwards. Pointer to member casts work
the opposite of normal pointer casts. There is an implicit D::*
to B::* conversion (no cast needed).

Nope. *You* got it backwards. Every member of 'B' is a member of
'D' as well (by inheritance), so 'B::*' ==> 'D::*' requires no special
syntax, its implicit (modulo all the "available" and "unambiguous"
cruft). [conv.mem]/2.

For the pointers to *objects*, however, it's reverse: Every 'D' is
a 'B' (modulo "available" and "unambiguous"), so conversion from 'D*'
to 'B*' is implicit.
A static_cast is needed
for D::* to B::*, however, and the using it is only legal if the
B it is used with is actually a D. Which is the case above. So
while the above may be as ugly as sin, it is perfectly legal
(and was used for callbacks in one GUI library---XViews, I
think---that was widely used in the past).

V
 
J

James Kanze

[...]
Nope. *You* got it backwards. Every member of 'B' is a member of
'D' as well (by inheritance), so 'B::*' ==> 'D::*' requires no special
syntax, its implicit (modulo all the "available" and "unambiguous"
cruft). [conv.mem]/2.
For the pointers to *objects*, however, it's reverse: Every 'D' is
a 'B' (modulo "available" and "unambiguous"), so conversion from 'D*'
to 'B*' is implicit.

Yes. There's a mix of misunderstanding what you wrote, and
mistyping what I wanted to say, there. What I wanted to say was
that the implicit conversions go in the opposite sense for
pointers to members, compared to pointers to objects (which you
just said in a lot clearer form), but that just like pointers to
objects, you can static_cast in the opposite sense of the
implicit conversion, *provided* the actual type is really a
derived; i.e. Base* -> Derived* is legal if the object pointed
to by Base* is actually a Derived, and Derived::* to Base::* is
legal if the resulting pointer to member is only used with the
address of a Base which is actually a Derived.

So you were wrong concerning undefined behavior. But to be
quite frank, I also thought it was undefined until I ran into a
large code base which used it.
 
J

James Kanze

More generally, once you cast it, you have to cast it back to its
original type to get well-defined behavior.

Not really. Consider:

struct Base
{
virtual ~Base() {}
} ;

struct Derived1 : public Base
{
int i ;
} ;

struct Derived2 : public Base
{
double d ;
} ;

int Base::* pmi = static_cast< int Base::* >( &Derived1::i ) ;
Base* pd1 = new Derived1 ;
Base* pd2 = new Derived2 ;
pd1->*pmi = 42 ; // legal and well defined
pd2->*pmi = 42 ; // undefined behavior.
 

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,767
Messages
2,569,572
Members
45,045
Latest member
DRCM

Latest Threads

Top