Topic: Casting member function pointers


Author: imp@boulder.parcplace.com (Warner Losh)
Date: Thu, 31 Mar 1994 17:59:45 GMT
Raw View
In article <9403302116.AA13896@ses.com> jamshid@ses.com (Jamshid
Afshar) writes:
>none of this "it'll work if you don't use VI" stuff.  Actually, if
>ANSI/ISO goes the latter route ("it doesn't work unless casted back")
>then I believe they should simply disallow casting of ptms so that
>compilers are at least given the opportunity to optimize them.

This is a horrible idea.

Given that I have a lirbrary that makes callbacks to other people's
code, you need this feature.

Consider an abstracted version of our interface:

 class B { public: virtual ~B(); };
 typedef (B::*memfnp)(void *);

 void
 set_f_cb(B* objp, memfnp mfp, void * argp) { /* stuff */ }

now, someone who wants to get member function callbacks on behalf of
their classes, merely needs to derive from B to get our software to
call the member.  To get something of type memfnp, they will have to
case their member functions.  We have no way of knowing what the class
is that they are using, but we do know the base class, so we can make
calls like
 objp->*mfp( argp )

It would be an undo burdon to have to cast back to the orginal object
before making this call.  Since objp is really the right object, and
is the common base class everything should be cool in most cases.

Granted, we run into problems with MI and VI, but that does not make
this any less useful when it does work.

>PS2: I'm very curious how ptm's were originally intended to be used.
>If they were intended to be used without being cast back to the
>original type, why didn't the ARM mention that they doesn't work with
>virtual inheritance?

We've been using typedef member functions pointers for about five
years now.  I think they were for things like this as we had a lot of
input on the design of C++ back then.  I don't know if this was one of
the things that we influanced or not, but we've made heavy use of it.

I'll be the first to grant this is a dark corner of the language.

Warner
--
Warner Losh  imp@boulder.parcplace.COM ParcPlace Boulder
"... but I can't promote you to "Prima Donna" unless you demonstrate a few
 more serious personality disorders"




Author: maxtal@physics.su.OZ.AU (John Max Skaller)
Date: Sat, 9 Apr 1994 23:20:03 GMT
Raw View
In article <9403302153.AA14338@ses.com> jamshid@ses.com (Jamshid Afshar) writes:
>In article <CMxBHx.JCn@ucc.su.oz.au>,
>John Max Skaller <maxtal@physics.su.OZ.AU> wrote:
>>The missing operation is "in-casting", that is, taking
>>a pointer to data member and "adding" on a pointer
>>to member of it, to get a pointer to member of the outer
>>type pointing to the inner member:
>
>> struct Inner { Mem mem; };
>> struct Outer { Inner inner; };
>> Inner Outer::* pin = &Outer::inner;
>> Mem Inner::* pmem = &Inner::mem;
>>
>> // now we can do this:
>> Mem Outer::* poutmem= &Outer::Inner::mem;

 WOOPS! See below!!
>
>I'm not sure how something like this (pointer to member of a member)
>can be implemented for pointer to member *functions*.

 Yes. Obviously, you cant incast from a pointer to
member function: its OK to cast from a data member to a function,
but it stope there.

 Note this means that a member of an included member subobject
can be considered a member of the enclosing class: in the example,
'mem' really IS a member of Outer, its just that we have to
name it 'Outer::inner.mem' so that its really 'Inner::mem' thats
the member of 'Outer'. 'mem' here can be a function. This is a weak
form of delegation.

>I don't think
>it's worth introducing into C++ unless there's some good uses for it
>and I don't think it's worth introducing an inconsistency between a
>pointer to data member and pointer to function member.

 I agree, but the committee didnt want nested functions. :-)

>Also, what if
>Outer contains two Inner objects?  Which `mem' would
>&Outer::Inner::mem pointe to?

>> Mem Outer::* poutmem= &Outer::Inner::mem;

 WOOPS. This is wrong. Its should read:

 Mem Outer::* poutmem= &Outer::inner.mem;

Sorry! Its 'inner.mem' thats the member of Outer.
>
>Sorry, but I don't think I'm "getting" your code.  Can you post a
>real-world example of how this might be used?

 struct Pair { int a, b; };
 struct Triple { Pair p; int c; };

 typedef int Triple::pint*;

 pint component(int i)
 {
  switch(i) {
   case 1: return &Triple::p.a;
   case 2: return &Triple::p.b;
   case 3: return &Triple::c;
  }
 }

 int getValue(Triple const& t, int index)
 {
  pint p = component(index);
  return t .* p;
 }

 void setValue(Triple const& t, int index, int value)
 {
  pint p = component(index);
  t .* p = value;
 }

The point here is that the exact data structure of Triple
is hidden in the 'component' function. This could be
really useful getting at members of a structure in
dialog boxes using a dynamic index.

--
        JOHN (MAX) SKALLER,         INTERNET:maxtal@suphys.physics.su.oz.au
 Maxtal Pty Ltd,      CSERVE:10236.1703
        6 MacKay St ASHFIELD,     Mem: SA IT/9/22,SC22/WG21
        NSW 2131, AUSTRALIA




Author: jamshid@ses.com (Jamshid Afshar)
Date: 30 Mar 1994 15:15:50 -0600
Raw View
In article <bgibbons-250394155517@bill-gibbons1.taligent.com>,
Bill Gibbons <bgibbons@taligent.com> wrote:
>In article <2mf5vg$f7s@vishnu.jussieu.fr>, clerc@gla.ecoledoc.ibp.fr
>(Fabrice Clerc) wrote:
>> If you use such a library, you simply can't use virtual or multiple
>> inheritance. Well, who needs MI anyway ? :-<
>
>The problem with MI was the one addressed in the Lippman & Stroustrup
>article.  Yes, not all compilers do this correctly.  But some do.  For
>example, I know that the Apple MPW C++ compiler does it correctly.

Just FYI, Brad Daniels has posted that DEC handles casted ptms of
classes using MI.  I know Watcom C++ also handles them well.  BC++ 4.0
and Cfront 3.0.2 do not.

>Virtual inheritance is different.  There is no good way to allow upcasts
>of pointers to members to virtual base classes in which the member does
>not exist. [...]
>Object pointers can't be downcast from virtual bases.  I see no problem
>with restrictions on member pointer upcasts to virtual bases.

No, pointers CAN be downcast from virtual bases by using RTTI.  As you
know, RTTI has been accepted by the committee and is already
implemented by some compilers.  I strongly believe that ANSI/ISO
should either allow the use of a casted ptm even when virtual
inheritance is involved *or* ANSI/ISO should completely disallow the
use of a casted ptm unless it is casted back to the original type --
none of this "it'll work if you don't use VI" stuff.  Actually, if
ANSI/ISO goes the latter route ("it doesn't work unless casted back")
then I believe they should simply disallow casting of ptms so that
compilers are at least given the opportunity to optimize them.

Virtual inheritance is an extremely useful C++ feature and it should
not be treated like an ugly stepchild just because most current C++
implementations do not implement RTTI yet.  In fact, one of the common
situations where people are currently casting pointers to members is
with GUI class libraries -- a situation where virtual inheritance has
proven very applicable.

Do you not like the method I presented of generating a callback
function which does a dynamic_cast?  There might even be other, more
efficient methods.  To recap my technique, the functions are generated
only at compile-time.  I think this is somewhat analogous to the
implementation technique required to implement covariant virtual
function return types when MI is involved.  An example of my
technique, which allows casted ptms to work even when virtual
inheritance is involved, is:

 struct VB {};
 struct D : public virtual VB { void d(); };
 typedef void (VB::*VBF)();

 void execute( VB* vb, VBF ptm ) { (vb->*ptm)(); }

 int main() {
    D d;
    execute( &d, static_cast<VBF>(&D::d) );
 }

The compiler generates a simple (non-virtual) "pretend" member
function of VB like:

 void _Call_D_d_on_VB( VB* this ) {
    D& r = dynamic_cast<D&>(*this);
    r.d();
 }

The result of the dynamic_cast in the call to execute() is a ptm that
actually points to _Call_D_d_on_VB().

PS: I don't really care what syntax is used for to get a
"well-behaved" ptms conversion.  dynamic_cast seems to be intended
only for class object pointers, otherwise I would say use it to get
"well-behaved" ptm casts.  I would be happy if old-style casts were
treated like reinterpret_cast and were not required to work in all
situations.

PS2: I'm very curious how ptm's were originally intended to be used.
If they were intended to be used without being cast back to the
original type, why didn't the ARM mention that they doesn't work with
virtual inheritance?

Jamshid Afshar
jamshid@ses.com




Author: jamshid@ses.com (Jamshid Afshar)
Date: 30 Mar 1994 15:53:10 -0600
Raw View
In article <CMxBHx.JCn@ucc.su.oz.au>,
John Max Skaller <maxtal@physics.su.OZ.AU> wrote:
>The missing operation is "in-casting", that is, taking
>a pointer to data member and "adding" on a pointer
>to member of it, to get a pointer to member of the outer
>type pointing to the inner member:

If I understand you correctly, this is interesting and it does seem
like it could be useful:

 class Button {
    void (*cb)(Button*);
 public:
    Button( void(*callback)(Button*) );  // regular function ptr
    void click() { (*cb)(this); }
 };

 class Dialog {
 public:
     Dialog() : help_button( &Dialog::helpcb ) {}
     void help();
 private:
    Button help_button;
    static void helpcb( Button* b ) {
       Dialog* me = &Dialog::help_button + b;
       me->help();
    }
 };

I have some questions about the example you presented, though:

> struct Inner { Mem mem; };
> struct Outer { Inner inner; };
> Inner Outer::* pin = &Outer::inner;
> Mem Inner::* pmem = &Inner::mem;
>
> // now we can do this:
> Mem Outer::* poutmem= &Outer::Inner::mem;

I'm not sure how something like this (pointer to member of a member)
can be implemented for pointer to member *functions*.  I don't think
it's worth introducing into C++ unless there's some good uses for it
and I don't think it's worth introducing an inconsistency between a
pointer to data member and pointer to function member.  Also, what if
Outer contains two Inner objects?  Which `mem' would
&Outer::Inner::mem pointe to?

> // and we can do this:
> Outer o;
> Inner& inr = o.inner;
> Mem& m = inr.mem;
>
> // and we can do this:
> Outer* po = &o;
> Inner* pi = &(po->inner);
> Mem* pm = &(pi->mem); // I abbreviate:"pi.mem"
>
> // but we cant do this:
> poutmem = pin.mem;
>
> // or this:
> poutmem = pin.*pmem;
>
>and at least one of those last two operations is mandatory:
>there is no other way to do them typesafely (I suppose you could
>just use "offsetof" everywhere :-)

Sorry, but I don't think I'm "getting" your code.  Can you post a
real-world example of how this might be used?

>In addition, all the implicit ptm conversions should be
>specified, and all the potentially safe casts be
>allowed for "static_cast<>".
>I think ptm downcasts MUST be allowed as implicit conversions.
>Corresponding upcasts should require a static_cast,
>but no guarrantees if virtual bases are used.
>If you want it to be right for virtual bases, a dynamic_cast
>is required.

Okay, as long as some kind of casts works for virtual inheritance I'll
be happy.  But, that would mean a change to dynamic_cast since it now
only works with pointers to class objects.

>Everything else is entirely unsafe and should require
>a reinterpret cast. This mirror ordinary pointers.
>(With up and down swapped)
>
>But, the results may be undefined: some implementations
>have multiple different implementations of ptms,
>depending on their type.

No, that's the whole point of my original article.  If we allow *any*
ptm cast to work it foils all optimization of ptms.  A compiler can't
optimize a ptm of a class with no virtual functions to just a regular
function pointer because the programmer might later derive a
complicated class from the simple base and expect the base ptm to be
big enough to hold a derived ptm.

>There is no generic pointer to member.
>That seems to mean safe conversion to a generic ptm and back
>again is not possible. (I opposed the resolution that there
>were no ptms of type X::void*, I think there should be).

A generic ptm might be useful if ANSI/ISO allows compilers to optimize
ptm size (any ptm casting is illegal).  I don't think a generic
class-specific ptm would be useful.

>What about const correctness? That seems to mirror
>the rules for pointers. Hence rules for implicit and
>const_cast.
>
>Finally, we need to affirm how ptms can be used:
>you must use the form
> &X::mem
>to take an address, and you must use the form
> this->*ptm
>to access the member: no shortcuts.

Okay with me.

Jamshid Afshar
jamshid@ses.com




Author: bgibbons@taligent.com (Bill Gibbons)
Date: Sat, 26 Mar 1994 00:07:12 GMT
Raw View
In article <2mf5vg$f7s@vishnu.jussieu.fr>, clerc@gla.ecoledoc.ibp.fr
(Fabrice Clerc) wrote:
> Let's take the XXXXX commercial library as an example.
> They handle callbacks with pointer to member functions
> and store these callbacks in a (Object::*)(some parameters) pointer
> (Object is the library "root-class" - you must derive from it if you want
> to be notifiable).
> Well, things work well with SI, but if you use MI or vitual inheritance,
> eg your client if of the form "class MyClient: public X, public Object",
> you get weird results with most compilers when trying to access your clients
> member variables when you are called back.
>
> There is no way they can downcast your pointer before activating the
> callback.
> If you use such a library, you simply can't use virtual or multiple
> inheritance. Well, who needs MI anyway ? :-<

The problem with MI was the one addressed in the Lippman & Stroustrup
article.  Yes, not all compilers do this correctly.  But some do.  For
example, I know that the Apple MPW C++ compiler does it correctly.

Virtual inheritance is different.  There is no good way to allow upcasts
of pointers to members to virtual base classes in which the member does
not exist.

But almost all of the restrictions on base <==> derived pointer conversions
have the direction of inheritance reversed for object pointers and member
pointers.  For example, object pointers upcast implicitly, and downcast
only
explicitly.  Member pointers downcast implicitly, and upcast only
explicitly.

Object pointers can't be downcast from virtual bases.  I see no problem
with restrictions on member pointer upcasts to virtual bases.


Bill Gibbons
bgibbons@taligent.com




Author: jamshid@ses.com (Jamshid Afshar)
Date: 18 Mar 1994 12:53:12 -0600
Raw View
In article <CMs46n.9ux@boulder.parcplace.com>,
Warner Losh <imp@boulder.parcplace.com> wrote:
>In article <CLsnvL.BE6@ses.com> jamshid@ses.com (Jamshid Afshar) writes:
>> void (Base::*bfp)() = (void(Base::*)()) &Derived::d;  // legal?
>
>Yes.  It is legal, and had better stay legal, or you break lots of
>people's code that assume that you can do this.
>I can give you the details of how OI and at least three other C++
>toolkits do this.  If you forbid this, it causes all kinds of grief.
>I always thought it was a slightly misleading statement in the ARM.

I'd be very interested to know if *any* real code that casts pointers
to member functions always casts the pointer back to the real,
original type before using the pointer.  The only time I've seen
anyone cast pointers to member functions is in situations like:

 class Button : public Window {
 public:
    typedef void (Window::*CB)();

    Button( Window& parent, CB callback )
       : _parent(parent), _callback(callback) {}

    void pressed() { (_parent.*_callback)(); }
 private:
    Window& _parent;
    CB _callback;
 };

 class MyWindow : public Window {
    Button _cancelb;
 public:
    MyWindow() : _cancelb( *this, (Button::CB) &MyWindow::cancel ) {}
    void cancel() { /*...*/ }
 };

But, if you do not cast back to the original member function type, you
may be surprised to know that your code breaks under almost all
existing implementations under certiain conditions, and more
importantly here in comp.std.c++, it's not correct according to the
January 1994 copy of the ANSI/ISO Working Paper which I just received.
So, Button::pressed() above results in undefined behavior (ie,
crashes).

5.2.8: A "pointer to member of class A of type T1" may be
 explicitly converted to a "pointer to member of class B of
 type T2" when class A and class B are either the same class or
 one is unambiguously derived from the other (4.6), and the
 types T1 and T2 are the same.

 The effect of calling a member function through a pointer to
 member function type that differs from the type used in the
 definition of the member function is undefined.

The T1==T2 restriction and the second paragraph are new and not in the
ARM.  The second paragraph must also be very important, since it's
repeated twice ;).  [std disclaimer: it's only a working paper, so
ignore all of this]

I can't tell you how happy I am that ANSI/ISO is making an effort to
clarify this subject.  Even the typo is a relief since I know that
someone is actively working on this issue.  The couple of times I've
discussed this subject with people working for compiler companies they
didn't acknowledge any problems with the ARM text and didn't think
they needed to bring this issue up in committee meetings.  Btw, a box
in 5.2.8 refers to a proposal on this subject.  Could someone please
forward this article to the person behind that proposal?  Also,
exactly what part of 4.6 is being referenced in the above text?

Anyway, I still have a beef with the new WP rule because in allowing
the practically useless casting (since you always have to cast back to
the original type), they foil the compiler writers attempt to optimize
the size of pointers to member functions based on the complexity of
the class.  Note: many compilers already optionally implement these
optimizations.

If ANSI/ISO were to not require casting of member function pointers
back and forth to work, compilers could use a more optimal
implementation than the examples in ARM 8.1.2c.  Pointers to member
functions of a class without virtual functions, or even no virtual
functions with that particular signature, could be implemented using
only a regular function pointer.  A vtbl index value would not be
needed. And no offset for the 'this' pointer would ever be needed
unless the class uses multiple inheritance.

If ANSI/ISO believes these optimizations are unimportant, fine, but
then why not give us pointer to member function casting that C++
programmers would actually find useful?  Allow pointers to member
functions to be used even if they call a member function belonging to
a class derived from the type of the pointer to member function's
class.  In other words, require that the example above work as long
as _parent has the member function pointed to by _callback.

Of course, calling a member function on an object which is not of that
class still results in undefined behavior.  I *believe* it's safely
and easily implemented and it doesn't impose overhead on users who
don't use this feature.  The hard (and necessary, IMO) part is making
it work when virtual inheritance is involved.


 struct VB { virtual void xxx(); };
 struct B : public virtual VB {};
 struct D : public B {
    void d();
 };
 void execute( VB* vb, void (VB::*mfp)() ) {
    (vb->*mfp)();  // how can we make sure D::d gets correct `this'?
 }
 int main() {
    D d;
    execute( &d, (void(VB::*)()) &D::d );
 }

The way to make this work is for the compiler to pass execute() a
"pretend" member function pointer that actually points to a compiler
generated static function:

 void __calling_D_d_on_a_VB( VB* __this ) {
    D* p = dynamic_cast<D*>(__this);
    if (!p) abort();  // undefined behavior, just like current rules
    p->d();
 }

Is that slick or am I not seeing a fundamental flaw?  Is anyone happy
with the current WP rule?

Jamshid Afshar
jamshid@ses.com




Author: clerc@gla.ecoledoc.ibp.fr (Fabrice Clerc)
Date: 19 Mar 1994 15:33:04 GMT
Raw View
In article <CMs46n.9ux@boulder.parcplace.com>, imp@boulder.parcplace.com (Warner Losh) writes:
|> In article <CLsnvL.BE6@ses.com> jamshid@ses.com (Jamshid Afshar) writes:
|> > void (Base::*bfp)() = (void(Base::*)()) &Derived::d;  // legal?
|>
|> Yes.  It is legal, and had better stay legal, or you break lots of
|> people's code that assume that you can do this.
|>
|> I can give you the details of how OI and at least three other C++
|> toolkits do this.  If you forbid this, it causes all kinds of grief.

The problem is already there.

Let's take the XXXXX commercial library as an example.
They handle callbacks with pointer to member functions
and store these callbacks in a (Object::*)(some parameters) pointer
(Object is the library "root-class" - you must derive from it if you want
to be notifiable).
Well, things work well with SI, but if you use MI or vitual inheritance,
eg your client if of the form "class MyClient: public X, public Object",
you get weird results with most compilers when trying to access your clients
member variables when you are called back.

There is no way they can downcast your pointer before activating the
callback.
If you use such a library, you simply can't use virtual or multiple
inheritance. Well, who needs MI anyway ? :-<


|> I always thought it was a slightly misleading statement in the ARM.
|>
|> Warner
|> --
|> Warner Losh  imp@boulder.parcplace.COM ParcPlace Boulder
|> "... but I can't promote you to "Prima Donna" unless you demonstrate a few
|>  more serious personality disorders"

--

Fabrice Clerc
clerc@gla.ecoledoc.ibp.fr   --  Speaking for myself





Author: maxtal@physics.su.OZ.AU (John Max Skaller)
Date: Sat, 19 Mar 1994 18:03:33 GMT
Raw View
In article <9403182303.AA02396@ses.com> jamshid@ses.com (Jamshid Afshar) writes:
>
>I just posted in another followup that the Jan 1994 WP further
>restricts the casting to only allow the cast if the "type" of the
>member pointed to is the same.  So, although the ARM seems to allow:
>
> struct Base { void f(int); };
> struct Derived : public Base { void g(double); };
> void (Base::*bfp)(int) = (void(Base::*)(int)) &Derived::g;
>                           // WP error: signatures don't match
>
>that cast is now considered a compile-time error.  I stated that the
>new WP rules also makes it illegal to call a member function through a
[]
>
>5.2.8: A "pointer to member of class A of type T1" may be
> explicitly converted to a "pointer to member of class B of
> type T2" when class A and class B are either the same class or
[]
>return type is undefined.  Such a restriction would be pointless since
>the first paragraph forbids casting between pointers to member
>functions with different signatures.
>
[]
>Well I'm glad someone expected the cast down to be required.  Maybe
>what I considered to be a gross oversight in analysis of pointer to
>member casting was really just an oversight in stating what was
>believed obvious?  In any case, there is a serious problem in the C++
>community right now.  Based on what I see in c.l.c++, gnu.g++.help,
>class libraries, and magazines, programmers do not know that they must
>cast back member function pointers to their original type before using
>them (I'm assuming that's what the WP says and what the ARM intends).
>Such code works a lot of the time with a lot of compilers, but we need
>a clear statement from the WP or compiler writers about what users can
>count on working now and in the future.  By future I mean with future
>(optimizing) compilers and with future modifications to the code --
>like making a function or base class virtual.
>
>Jamshid Afshar
>jamshid@ses.com

 Well, I apologise to all those who didnt know I promised
to write a paper on pointer to members but didnt get it done
in time for the San Diego meeting. Bill Gibbons was also interested.
It appears Jamshid Afshar is also interested.

 The intent was to clarify all pointer to member
issues and introduce a crucial missing operation.
The missing operation is "in-casting", that is, taking
a pointer to data member and "adding" on a pointer
to member of it, to get a pointer to member of the outer
type pointing to the inner member:

 struct Inner { Mem mem; };
 struct Outer { Inner inner; };
 Inner Outer::* pin = &Outer::inner;
 Mem Inner::* pmem = &Inner::mem;

 // now we can do this:
 Mem Outer::* poutmem= &Outer::Inner::mem;

 // and we can do this:
 Outer o;
 Inner& inr = o.inner;
 Mem& m = inr.mem;

 // and we can do this:
 Outer* po = &o;
 Inner* pi = &(po->inner);
 Mem* pm = &(pi->mem); // I abbreviate:"pi.mem"

 // but we cant do this:
 poutmem = pin.mem;

 // or this:
 poutmem = pin.*pmem;

and at least one of those last two operations is mandatory:
there is no other way to do them typesafely (I suppose you could
just use "offsetof" everywhere :-)

In addition, all the implicit ptm conversions should be
specified, and all the potentially safe casts be
allowed for "static_cast<>".

I think ptm downcasts MUST be allowed as implicit conversions.
Corresponding upcasts should require a static_cast,
but no guarrantees if virtual bases are used.
If you want it to be right for virtual bases, a dynamic_cast
is required.

Everything else is entirely unsafe and should require
a reinterpret cast. This mirror ordinary pointers.
(With up and down swapped)

But, the results may be undefined: some implementations
have multiple different implementations of ptms,
depending on their type.
There is no generic pointer to member.
That seems to mean safe conversion to a generic ptm and back
again is not possible. (I opposed the resolution that there
were no ptms of type X::void*, I think there should be).

What about const correctness? That seems to mirror
the rules for pointers. Hence rules for implicit and
const_cast.

Finally, we need to affirm how ptms can be used:
you must use the form

 &X::mem

to take an address, and you must use the form

 this->*ptm

to access the member: no shortcuts.


--
        JOHN (MAX) SKALLER,         INTERNET:maxtal@suphys.physics.su.oz.au
 Maxtal Pty Ltd,      CSERVE:10236.1703
        6 MacKay St ASHFIELD,     Mem: SA IT/9/22,SC22/WG21
        NSW 2131, AUSTRALIA




Author: jamshid@ses.com (Jamshid Afshar)
Date: 18 Mar 1994 17:03:46 -0600
Raw View
In article <0hVXjO600gjOJ4A5kU@andrew.cmu.edu>,
Robert Andrew Ryan  <rr2b+@andrew.cmu.edu> wrote:
>> I believe the intention of 5.4 is that the explicit conversion is only
>> allowed when the implicit conversion is legal.  I believe the
>> statement "... when one of which is unambiguously derived from the
>> other ($4.6)" is a parenthetical remark, like "... in this situation,
>> see 4.6 for details".
>
>I believe it is intended to be useful, the commentary seems to indicate
>that this is a hole needed for some applications.  Mainly the same kind
>of things you would do in C with void (*)() function pointers that
>really aren't.

Take a very close look at that example in the commentary on p70.  It
says there is "no void* for pointers to members.  This implies that
casting of POINTERS TO pointers to members must be used if a generic
"pointer to any member of any class is needed."  The example is like:

 typedef void Z::* any_ptom;
 void push_ptom( any_ptom* a );
 void f( int X::* p ) {
    push_ptom( (any_ptom*) p );  // note -- no pointer to member casting
 }

What point were S&E trying to make here?  Why would anyone want to use
a *pointer* to an unrelated pointer to a member instead of just using
a void*?  I guess the ARM is just saying that there's no way to store
a pointer to member of a particular class in a pointer to member of an
unrelated class.  Why not just say: if you need a generic pointer to
member, tough, just pass around a void pointer which points to the
pointer to members and remember to cast back to the original type
before using it.

I just posted in another followup that the Jan 1994 WP further
restricts the casting to only allow the cast if the "type" of the
member pointed to is the same.  So, although the ARM seems to allow:

 struct Base { void f(int); };
 struct Derived : public Base { void g(double); };
 void (Base::*bfp)(int) = (void(Base::*)(int)) &Derived::g;
                           // WP error: signatures don't match

that cast is now considered a compile-time error.  I stated that the
new WP rules also makes it illegal to call a member function through a
pointer to member that is not the correct "type":

 struct B { void f(); };
 struct D : public B { void g(); };
 int main() {
    Derived d;
    void (D::*dfp)() = &D::g;
    void (B::*bfp)() = (void(B::*)()) &D::g;
    (d.*dfp)();  // okay
    (d.*bfp)();  // I think results in undefined behavior
 }

I still believe this is the intent, but I have received email from
someone who has disagreed with my interpretation of the second
paragraph of the new WP rules:

5.2.8: A "pointer to member of class A of type T1" may be
 explicitly converted to a "pointer to member of class B of
 type T2" when class A and class B are either the same class or
 one is unambiguously derived from the other (4.6), and the
 types T1 and T2 are the same.

 The effect of calling a member function through a pointer to
 member function type that differs from the type used in the
 definition of the member function is undefined.

The question is what is the "type" of a member function?  Does the
type include the type of `this'?  If so, that makes the first
paragraph useless since T1 would never equal T2 for member functions
of different classes.  If "type" does not include the type of `this',
then what does the second paragraph mean by "the type used in the
definition of the member function"?  It can't be saying that the
effect of calling a member function with a different signature or
return type is undefined.  Such a restriction would be pointless since
the first paragraph forbids casting between pointers to member
functions with different signatures.

>>  struct Base {};
>>  struct Derived : virtual public Base {
>>     void d();
>>  };
>>  void call( Base* b, void (Base::*mfp)() ) {
>>     (b->*mfp)();  // legal code, but run-time error -- D::d() doesn't
>>                   // get the correct `this'
>>  }
>
>I would expect that if mfp above is really a pointer to a derived member
>you should perform an explicit cast down before attempting the call.

Well I'm glad someone expected the cast down to be required.  Maybe
what I considered to be a gross oversight in analysis of pointer to
member casting was really just an oversight in stating what was
believed obvious?  In any case, there is a serious problem in the C++
community right now.  Based on what I see in c.l.c++, gnu.g++.help,
class libraries, and magazines, programmers do not know that they must
cast back member function pointers to their original type before using
them (I'm assuming that's what the WP says and what the ARM intends).
Such code works a lot of the time with a lot of compilers, but we need
a clear statement from the WP or compiler writers about what users can
count on working now and in the future.  By future I mean with future
(optimizing) compilers and with future modifications to the code --
like making a function or base class virtual.

Jamshid Afshar
jamshid@ses.com




Author: tom@smart.bo.open.de (Thomas Neumann)
Date: 20 Mar 1994 15:07:18 GMT
Raw View
>>>>> "Fabrice" == Fabrice Clerc <clerc@gla.ecoledoc.ibp.fr> writes:

    Fabrice> The problem is already there.
    Fabrice> Let's take the XXXXX commercial library as an example.
    Fabrice> They handle callbacks with pointer to member functions
    Fabrice> and store these callbacks in a (Object::*)(some
    Fabrice> parameters) pointer (Object is the library "root-class" -
    Fabrice> you must derive from it if you want to be notifiable).
    Fabrice> Well, things work well with SI, but if you use MI or
    Fabrice> vitual inheritance, eg your client if of the form "class
    Fabrice> MyClient: public X, public Object", you get weird results
    Fabrice> with most compilers when trying to access your clients
    Fabrice> member variables when you are called back.

What about making the `callback object' a template class that
is paramterized over the type of the object being called back,
and, to decouple the caller from the concrete type of the callee,
derive the callback object template class from a common base class.

I'd like to hear some opinions on the usefulness of the approach
outlined by the code below:


//----------------------------------------------------------------------
#include <iostream.h>

class GUI_CallbackBase
{
public:
    virtual ~GUI_CallbackBase() {}
    virtual void invoke() = 0;

protected:
    GUI_CallbackBase(void *iData) : myData(iData) {}
    void *myData;
};


template<class T> class GUI_Callback : public GUI_CallbackBase
{
public:
    GUI_Callback(T *theObject, void (T::*theMethod)(void *), void *theData=0)
 : GUI_CallbackBase(theData),
   myObject(theObject),
   myMethod(theMethod)
    {}

    void invoke() { (myObject->*myMethod)(myData); }

private:
    T *myObject;
    void (T::*myMethod)(void *);
};


class GUI_App
{
public:
    GUI_App(int theID) : myID(theID) {}
    void Quit(void *) { cerr << "GUI_App[" << myID << "]::Quit()\n"; }

private:
    int myID;
};


int
main()
{
    GUI_App app(42);

    GUI_CallbackBase *appQuitCB
 = new GUI_Callback<GUI_App>(&app, &GUI_App::Quit);

    appQuitCB->invoke();

    return 0;
}
//----------------------------------------------------------------------




Author: daniels@biles.com (Brad Daniels)
Date: Mon, 21 Mar 1994 14:19:59 GMT
Raw View
In article <9403182303.AA02396@ses.com>,
Jamshid Afshar <jamshid@ses.com> wrote:
...
>Well I'm glad someone expected the cast down to be required.  Maybe
>what I considered to be a gross oversight in analysis of pointer to
>member casting was really just an oversight in stating what was
>believed obvious?  In any case, there is a serious problem in the C++
>community right now.  Based on what I see in c.l.c++, gnu.g++.help,
>class libraries, and magazines, programmers do not know that they must
>cast back member function pointers to their original type before using
>them (I'm assuming that's what the WP says and what the ARM intends).
>Such code works a lot of the time with a lot of compilers, but we need
>a clear statement from the WP or compiler writers about what users can
>count on working now and in the future.  By future I mean with future
>(optimizing) compilers and with future modifications to the code --
>like making a function or base class virtual.

As I think I mentioned in e-mail, at least two major implementations
(DEC C++ and HP C++) fully support casting a pointer to a member function
to a pointer to a member function of one of its base classes.  They support
it with both single and multiple inheritance.  DEC C++ gives an error in the
virtual inheritance case, while HP C++ (which is Cfront-based) just produces
incorrect results.  The ARM and working paper are unclear on this point, but
the existence of two implementations which support using the pointers without
casting them back to their original type prove that such a requirement is not
necessary.  Indeed, it is usually not possible or desirable for the programmer
to know the original type of the object or the member function pointer.  The
virtual inheritance case could be implemented using the static function hack
you sent mail about, so it would proabably be a good idea to explicitly allow
it.

- Brad
------------------------------------------------------------------------
+ Brad Daniels                  | "Let others praise ancient times;    +
+ Biles and Associates          |  I am glad I was born in these."     +
+ These are my views, not B&A's |           - Ovid (43 B.C. - 17 A.D.) +
------------------------------------------------------------------------




Author: daniels@biles.com (Brad Daniels)
Date: Tue, 15 Mar 1994 21:46:06 GMT
Raw View
In article <CLsnvL.BE6@ses.com>, Jamshid Afshar <jamshid@ses.com> wrote:
>ARM 5.4 says "A pointer to member may be explicitly converted to a
>different pointer to member type when the two types are both pointers
>to members of the same class or when the two types are pointers to
>member functions of calsses one of which is unambiguously derived from
>the other ($4.6)".
...
>The question I have is whether the conversion is legal with an
>explicit cast:
>
> void (Base::*bfp)() = (void(Base::*)()) &Derived::d;  // legal?

As I have been interpreting it, and as the DEC C++ folks interpreted
it, it is legal.

...
>If I am wrong and the intention is that the explicit conversion above
>is legal, then Stroustrup&Ellis did a very poor job in not explaining
>the numerous of situations in which the explicit conversions simply
>can't work.  Note, the rest of 5.4 "Explicit Type Conversions" does
>mention requirements or "gotchas" for other kinds of explicit
>conversions.

Virtual inheritance is the one case where it can't work.  DEC C++
reports a diagnostic, but you are quite right, as far as I can tell,
that Stroustrup and Ellis don't mention this restriction.  I originally
thought that it also couldn't work in the presence of multiple inheritance,
but that is not the case.  Part of the member function pointer can be an
offset value indicating the amount of offset to add to a "this" of the
given class to get a "this" of the member function's class.  I hope I phrased
that intelligibly...  For example, if C inherits from A and B, and the
B part of C is offset by 4 bytes from the address of the object, a pointer
to a member of C when cast to a pointer to a member of B would include an
offset of -4 to indicate where a C object is when given a pointer to its
B member.

DEC C++ implements both the single and multiple cases correctly, and gives
an error when confronted with virtual inheritance.  When I insert the
"virtual" keyword in your example, I get:

foo.C:18: error: In this statement, the class type of the pointer to member
                 value "&D::d" is "D", which has "B" as a virtual base class.
Compilation terminated with errors.

>So, what's the problem: a slightly misleading statement in the ARM, or
>a gross failure to consider (or at least document) the results of
>allowing void(Derived::*)() => void(Base::*)() conversions?

I would guess the latter, based on other information, but I suppose only
Stroustrup or Ellis could answer that one deifnitively....  :-)

- Brad
------------------------------------------------------------------------
+ Brad Daniels                  | "Let others praise ancient times;    +
+ Biles and Associates          |  I am glad I was born in these."     +
+ These are my views, not B&A's |           - Ovid (43 B.C. - 17 A.D.) +
------------------------------------------------------------------------




Author: rfb@lehman.com (Rick Busdiecker)
Date: Tue, 22 Mar 1994 20:30:56 GMT
Raw View
In article <9403181852.AA29618@ses.com> jamshid@ses.com (Jamshid Afshar) writes:

   From: jamshid@ses.com (Jamshid Afshar)
   Date: 18 Mar 1994 12:53:12 -0600

   I'd be very interested to know if *any* real code that casts pointers
   to member functions always casts the pointer back to the real,
   original type before using the pointer.

Yes.  I've implemented a facility that allows access to object
attributes by name.  In the implementation, there is an attribute
class which holds a name (string), a get/set pair of pointers to
pointer-to-member-function objects (following the idea presented on
ARM, p. 70), and a reference to an instance of a type-identifier
class.  By using the type-identifier and consistently following a few
conventions, the member function is called only after being cast to
it's original type.  It's not especially pretty (this is C++ afterall
:-) however the blecherous syntax is not exposed in the API and it
gets the job done.

--
Rick Busdiecker <rfb@lehman.com> and <rfb@cmu.edu>
  Lehman Brothers
  388 Greenwich Street
  New York, NY 10013        ``I hate quotations.''  Ralph Waldo Emerson
  (212) 464-4750




Author: lchen@cpdsc.com (Lin Chen)
Date: 24 Mar 1994 18:50:08 GMT
Raw View
I DON'T HAVE FTP CAPABILITY. CAN SOME KIND SOUL E-MAIL ME A COPY.

THANKX.











Author: bgibbons@taligent.com (Bill Gibbons)
Date: Thu, 24 Mar 1994 20:20:12 GMT
Raw View
In article <CMxBHx.JCn@ucc.su.OZ.AU>, maxtal@physics.su.OZ.AU (John Max
Skaller) wrote:
> ...
> Finally, we need to affirm how ptms can be used:
> you must use the form
>
>  &X::mem
>
> to take an address, and you must use the form
>
>  this->*ptm
>
> to access the member: no shortcuts.

This position is contrary to what is implied by the ARM and explicitly
stated elsewhere.  It is neither necessary nor existing practice.

For example, see:

   Pointers to Class Members in C++
   Lippman & Stroustrup
   1988 USENIX C++ Conference

There are very good reasons for allowing upcasts of pointers to members
to base classes where the members do not exist, and then dereferencing
them with base class pointers which actually point to classes which
contain the members.  The implementation is only slightly hard.

This is an open standards issue.

Bill Gibbons
bgibbons@taligent.com




Author: Robert Andrew Ryan <rr2b+@andrew.cmu.edu>
Date: Tue, 15 Mar 1994 17:51:06 -0500
Raw View
Excerpts from netnews.comp.std.c++: 15-Mar-94 Casting member function
poi.. Jamshid Afshar@ses.com (3257)

> I believe the intention of 5.4 is that the explicit conversion is only
> allowed when the implicit conversion is legal.  I believe the
> statement "... when one of which is unambiguously derived from the
> other ($4.6)" is a parenthetical remark, like "... in this situation,
> see 4.6 for details".

I believe it is intended to be useful, the commentary seems to indicate
that this is a hole needed for some applications.  Mainly the same kind
of things you would do in C with void (*)() function pointers that
really aren't.

Excerpts from netnews.comp.std.c++: 15-Mar-94 Casting member function
poi.. Jamshid Afshar@ses.com (3257)

> It's simply impossible to use the result of a Derived => Base pointer
> to member function conversion when virtual inheritance is involved.

>  struct Base {};
>  struct Derived : virtual public Base {
>     void d();
>  };
>  void call( Base* b, void (Base::*mfp)() ) {
>     (b->*mfp)();  // legal code, but run-time error -- D::d() doesn't
>                   // get the correct `this'
>  }

I would expect that if mfp above is really a pointer to a derived member
you should perform an explicit cast down before attempting the call.

-Rob




Author: pkt@lpi.liant.com (Scott Turner)
Date: Wed, 16 Mar 1994 16:45:33 GMT
Raw View
In article <CLsnvL.BE6@ses.com>, jamshid@ses.com (Jamshid Afshar) wrote:

> ARM 5.4 says "A pointer to member may be explicitly converted to a
> different pointer to member type when the two types are both pointers
> to members of the same class or when the two types are pointers to
> member functions of calsses one of which is unambiguously derived from
> the other ($4.6)".

>  struct Base {
>     void b();
>  };
>  struct Derived : public Base {
>     void d();
>  };
>  void (Base::*bfp)() = &Derived::d;  // error
>
> The question I have is whether the conversion is legal with an
> explicit cast:
>
>  void (Base::*bfp)() = (void(Base::*)()) &Derived::d;  // legal?
>
> I believe the intention of 5.4 is that the explicit conversion is only
> allowed when the implicit conversion is legal.

I wouldn't suppose that.  The wording explicitly allows either class
to be derived from the other, which is not supported by the implicit
conversions.

Your example is valid at compile-time, but is an execution-time error
because Derived::d is not a member of Base.   The explicit conversion
     extern void (Derived::*dfp)();
     void (Base::*bfp2)() = (void(Base::*)())dfp;
may be valid as long as dfp points to a member of Base.  The implicit
conversion
     void (Base::*bfp3)() = dfp;
is always a compile-time error.

> Note, the rest of 5.4 "Explicit Type Conversions" does
> mention requirements or "gotchas" for other kinds of explicit
> conversions.

Yes, this omission opens things up for the kinds of questions you're
raising.  But it is proper to infer the "gotchas" from the fact that
the type
     void (Base::*)()
can only represent pointers to members of class Base.

> It's simply impossible to use the result of a Derived => Base pointer
> to member function conversion when virtual inheritance is involved.

No ... It's simply tricky to implement.

>     (b->*mfp)();  // legal code, but run-time error -- D::d() doesn't
>                   // get the correct `this'

> Furthermore, if the explicit conversion is allowed, that completely
> blows away any chance for compilers to optimize the size and speed of
> pointers to member functions based on the complexity of a class (eg,
> any virtual functions? is virtual inheritance used?).

Here you're on the right track: compilers are permitted to make this
optimization.  This reason (not virtual inheritance) is why both of
your examples are invalid.

> So, what's the problem: a slightly misleading statement in the ARM, or
> a gross failure to consider (or at least document) the results of
> allowing void(Derived::*)() => void(Base::*)() conversions?

1. Not a misleading statement.
2. The failure is not necessarily a gross one, though I'll confess that it
has
   caused me a portion of trouble as a compiler implementer.
----
Scott Turner
Liant Software Corp.
959 Concord St., Framingham, MA 01701  USA
(508)872-8700
pkt@lpi.liant.com




Author: imp@boulder.parcplace.com (Warner Losh)
Date: Wed, 16 Mar 1994 22:37:35 GMT
Raw View
In article <CLsnvL.BE6@ses.com> jamshid@ses.com (Jamshid Afshar) writes:
> void (Base::*bfp)() = (void(Base::*)()) &Derived::d;  // legal?

Yes.  It is legal, and had better stay legal, or you break lots of
people's code that assume that you can do this.

I can give you the details of how OI and at least three other C++
toolkits do this.  If you forbid this, it causes all kinds of grief.

I always thought it was a slightly misleading statement in the ARM.

Warner
--
Warner Losh  imp@boulder.parcplace.COM ParcPlace Boulder
"... but I can't promote you to "Prima Donna" unless you demonstrate a few
 more serious personality disorders"




Author: jamshid@ses.com (Jamshid Afshar)
Date: Fri, 25 Feb 1994 19:09:20 GMT
Raw View
ARM 5.4 says "A pointer to member may be explicitly converted to a
different pointer to member type when the two types are both pointers
to members of the same class or when the two types are pointers to
member functions of calsses one of which is unambiguously derived from
the other ($4.6)".

ARM 4.6 doesn't discuss pointers to members, but the commentary points
to 4.8 which does discuss them.  4.8 only allows conversions from Base
pointers to members to Derived pointers to members, not the other way
around:

 struct Base {
    void b();
 };
 struct Derived : public Base {
    void d();
 };
 void (Base::*bfp)() = &Derived::d;  // error

The question I have is whether the conversion is legal with an
explicit cast:

 void (Base::*bfp)() = (void(Base::*)()) &Derived::d;  // legal?

I believe the intention of 5.4 is that the explicit conversion is only
allowed when the implicit conversion is legal.  I believe the
statement "... when one of which is unambiguously derived from the
other ($4.6)" is a parenthetical remark, like "... in this situation,
see 4.6 for details".

If I am wrong and the intention is that the explicit conversion above
is legal, then Stroustrup&Ellis did a very poor job in not explaining
the numerous of situations in which the explicit conversions simply
can't work.  Note, the rest of 5.4 "Explicit Type Conversions" does
mention requirements or "gotchas" for other kinds of explicit
conversions.

It's simply impossible to use the result of a Derived => Base pointer
to member function conversion when virtual inheritance is involved.

 struct Base {};
 struct Derived : virtual public Base {
    void d();
 };
 void call( Base* b, void (Base::*mfp)() ) {
    (b->*mfp)();  // legal code, but run-time error -- D::d() doesn't
                  // get the correct `this'
 }
 main() {
    D d;
    call( d, (void(Base::*)()) &Derived::d );
 }

See the appended program for an even simpler example not using virtual
inheritance that doesn't work on any compiler I have tried.

Furthermore, if the explicit conversion is allowed, that completely
blows away any chance for compilers to optimize the size and speed of
pointers to member functions based on the complexity of a class (eg,
any virtual functions? is virtual inheritance used?).  If that's the
case, C++ might as well allow casting of any pointer to member
function to any other type to be analogous to regular function
pointers.

So, what's the problem: a slightly misleading statement in the ARM, or
a gross failure to consider (or at least document) the results of
allowing void(Derived::*)() => void(Base::*)() conversions?

Jamshid Afshar
jamshid@ses.com

-------

  #include <iostream.h>

  class B {};

  class D : public B {  // you might have to use virtual inheritance
     int i;
  public:
     D() : i(42) {}
     virtual void d() { cout << "D::d(), i=" << i << endl; }
  };

  void call( B* b, void (B::*mfp)() )
  { (b->*mfp)(); }

  main() {
     D d;
     d.d();                           // outputs "D::d(), i=42"
     call( &d, (void(B::*)())&D::d ); // outputs "D::d(), i=[garbage]" or crashes
          // I don't think the ARM considers this cast legal and
          // using the resulting pointer certainly can't work.
     return 0;
  }