Why pointer to member function?

B

Ben

programmer already knows that he _can_ do it and that it will work

Let's make sure what the "already" means.

If we are talking about "why PTM in the first place", then we
programmers DID not already know that we can do it at the time before
the 2nd draft of PTM was given birth.

And we did know that casting function pointers from 'void(*)(C*)' to
'void(*)(B*)' is not gonna work.


But all these "what-if" are none-sense anyway. The creature has been
released and killing it will result in broken code (if not the end of
the world).

I just feel pity that I have to pay price for something that I would
never want to use. Does it contradict with the slogan: no use, no
overhead?


Thanks again for the accurate and detailed explanation.

And have a nice weekend!
 
B

Ben

You can read about it in 5.2.10/9. But note that that's not
"cross-casting between any two PTM". That would be neither safe nor
portable. Safe and portable is the round-trip 'renterpret_cast'.
"Round-trip" is this case means that you may cast one PMF type to
another PMF type, but you have to cast it back to the original PMF type
before attempting to use it as PMF.

(Wasn't it already mentioned in this discussion before? OR am I thinking
about some other discussion?)

That is exactly what I want. I actually don't really need to cast PTM
a to PTM b and use it as PTM b. If the roundtrip from any PTM to void
PTM and back to that PTM is safe, I'm happy.

However, I just can't see how this can be true. If PTM a and PTM b has
different size, it means that one way in the roundtrip is losing
information. How can the roundtrip be safe?

Does the standard say that roundtrip between ANY two PTMs is safe? Or
it is only between sub/super classes?
 
A

Andrey Tarasevich

Ben said:
That is exactly what I want. I actually don't really need to cast PTM
a to PTM b and use it as PTM b. If the roundtrip from any PTM to void
PTM and back to that PTM is safe, I'm happy.

Yes, it is safe, assuming that by "void PMF type" you mean something
like 'void (SomeClass::*)()', not 'void (*)()' or 'void*'.
However, I just can't see how this can be true. If PTM a and PTM b has
different size, it means that one way in the roundtrip is losing
information. How can the roundtrip be safe?

The implementation is required not to loose any information in the
round-trip 'reinterpert_cast' conversion. What steps the implementation
takes to satisfy that requirement is its internal business. One is not
supposed to worry about it. In practice that normally means that all
PMFs have the same size, even though it isn't formally required by the
language specification.
Does the standard say that roundtrip between ANY two PTMs is safe? Or
it is only between sub/super classes?

It is safe between any two _PMFs_ (pointer-to-member-function) types. As
it is safe between any two pointer-to-ordinary-function types.

With object pointers (both regular pointers and
pointers-to-data-members) alignment requirements come into play and
situation becomes slightly different. Original pointer value is
guaranteed to be preserved in a round-trip 'reinterpert_cast' conversion
only if alignment requirements of the second type are no stricter than
those of the original type.
 
B

Ben

It is safe between any two _PMFs_ (pointer-to-member-function) types. As
it is safe between any two pointer-to-ordinary-function types.

This is the test code I did in VC7.0:

struct Dummy{};
typedef void(Dummy::*VoidPtm)();

struct A{
void f(int i){}
};
struct B{
int f(){return 1;}
};
struct C: public A, public B{
int g(int i,int j){
cout <<"C::g"<<endl;
return i;
}
};

void test(){
C c;
VoidPtm p = reinterpret_cast<VoidPtm>(A::f);
p = reinterpret_cast<VoidPtm>(B::f);
p = reinterpret_cast<VoidPtm>(C::g);//DOES NOT COMPILE!
}

And if I comment out "public B" from class C, it compiles.

Do you think VC7 is just not conformant to the standard?
 
B

Bob Hairgrove

This is the test code I did in VC7.0:

I made some minor changes and it compiled OK with the Borland 5.5.1
free command line compiler:

===============
#include <iostream>
#include said:
struct Dummy{};
typedef void(Dummy::*VoidPtm)();

struct A{
void f(int i){}
};
struct B{
int f(){return 1;}
};
struct C: public A, public B{
int g(int i,int j){
// cout <<"C::g"<<endl;
std::cout <<"C::g"<< std::endl;
return i;
}
};

void test(){
C c;
VoidPtm p = reinterpret_cast<VoidPtm>(A::f);
p = reinterpret_cast<VoidPtm>(B::f);
p = reinterpret_cast<VoidPtm>(C::g);//compiles OK now.
}

================

Results:

bcc32 -c ptmf_test.cpp
Borland C++ 5.5.1 for Win32 Copyright (c) 1993, 2000 Borland
ptmf_test.cpp:
Warning W8004 ptmf_test.cpp 29: 'p' is assigned a value that is never
used in function test()
 
B

Ben

Results:
bcc32 -c ptmf_test.cpp
Borland C++ 5.5.1 for Win32 Copyright (c) 1993, 2000 Borland
ptmf_test.cpp:
Warning W8004 ptmf_test.cpp 29: 'p' is assigned a value that is never
used in function test()

The question is: does this have a standard? or it is all
implementation dependent?

From what VC is doing: using differnet size for MI, it makes sense to
disallow this.

And if bcc uses same size, it certainly can allow this.

If standard does not make a guarantee here, I'll have to assume that
it is NOT safe for a roundtrip between any two PTMs, generally
speaking.
 
I

Ingo Nolden

I read this whole thread and feel this is a good place to drop in.

I have an issue that is similar to Ben's.

My problem are my serializer methods. Since I want the everyday
programming in my class library that may grow as simple as possible, I
want only two member functions:
Object::Serialize( IFormatter& f );
Object::DeSerialize( IFormatter& f );

they contain some code to write to or read Data from formatter.

They also invoke the serialization of the base class. But any managemant
and tracking of object sized in the stream is done in the formatter.
That means, the base class serializer cannot be called directly. It
would just put its data into the stream without marking the end of the
base. This will result in the fact that I cannot add serialized
veriables to the base after the release of the program, or I would have
to write converter to read and write to documents from an older version.
And the older version cannot read docs from the newer one.
So what I want to do is passing a member function pointer into the
stream that points to the serializer of the base class.
Hmm, sounds weird? Ideas are welcome.

So, my problem regarding the function pointer is, that the functions are
virtual. They have to be virtual, because if the object is serialized
through a base class (this)pointer, I want the complete object to be
serialized.
If the formatter is calling the serializer function through the function
pointer it needs an object pointer of the type of the common base class
for all objects, pointing to the object to serialize. And it needs a
function pointer to the serializer. But due to the virtual function it
calls always the function of the complete type, which would rather
result in an endless loop, than in what I want.
So, what I need is to have a pointer that points to a function rather to
( how I understand it ) the entry in the vftable.
So, is it true that this last point is the same as the issue with the
first and the second draft of the standard? Or am I wrong. Or is there
another way to have the kind of pointer that I want.
I thought about using a static wrapper function, but it would also call
the function of the complete type.
The only way I know to make it work is having the static for the
function pointer and a virtual member function that calls the static.
But it is still more arkward than I wanted.

So, can you spread some light in the dark for me?

Ingo
 
A

Andrey Tarasevich

Ben said:
This is the test code I did in VC7.0:

struct Dummy{};
typedef void(Dummy::*VoidPtm)();

struct A{
void f(int i){}
};
struct B{
int f(){return 1;}
};
struct C: public A, public B{
int g(int i,int j){
cout <<"C::g"<<endl;
return i;
}
};

void test(){
C c;
VoidPtm p = reinterpret_cast<VoidPtm>(A::f);
p = reinterpret_cast<VoidPtm>(B::f);
p = reinterpret_cast<VoidPtm>(C::g);//DOES NOT COMPILE!
}

And if I comment out "public B" from class C, it compiles.

Do you think VC7 is just not conformant to the standard?

My experience with VC is limited to VC6 and I don't know how much it is
different from VC7 in this respect. VC6 happens to support several PMF
models. The default one is called "Best-case always", which means that
VC6 is trying to optimize the size of PMFs by analyzing the class
hierarchy and choosing the size of the PMF accordingly. The logic it
uses is simple: if there's no multiple inheritance in the given class
and any of its ancestors, then the only thing PMFs to member functions
of this class need to store is the function's entry point (4 bytes
long). No additional information is needed. Otherwise (i.e. there's
multiple inheritance somewhere above in the hierarchy) VC6 would use
fully functional PMFs (8 bytes long).

It is easy to demonstrate that this VC6's logic is flawed and will lead
to non-standard conforming behavior in certain contexts (my original
example is one of such contexts, 'reinterpret_cast' round-trip is
another, just to name a few). However, there are a couple of compiler
options that will force VC6 to always use
multiple-inheritance-compatible PMFs regardless of the inheritance
structure (see '/vmg', '/vmm').

I'm pretty sure (although I could be wrong) that what you observe in
your experiment with VC7 happens for similar reasons. The source and
destination pointer types are so different internally that even
'reinterpret_cast' refuses to convert one into another. Maybe you should
check the compiler's documentation and see whether if describes some
options similar to '/vm...' in VC6.
 
A

Andrey Tarasevich

Ben said:
...
void test(){
C c;
VoidPtm p = reinterpret_cast<VoidPtm>(A::f);
p = reinterpret_cast<VoidPtm>(B::f);
p = reinterpret_cast<VoidPtm>(C::g);//DOES NOT COMPILE!
}
...

BTW, the explicit application of 'address-of' operator is required in
this context

VoidPtm p = reinterpret_cast<VoidPtm>(&A::f);
p = reinterpret_cast<VoidPtm>(&B::f);
p = reinterpret_cast<VoidPtm>(&C::g);
 
J

Jeff Flinn

Ingo Nolden said:
I read this whole thread and feel this is a good place to drop in.

I have an issue that is similar to Ben's.

My problem are my serializer methods. Since I want the everyday
programming in my class library that may grow as simple as possible, I
want only two member functions:
Object::Serialize( IFormatter& f );
Object::DeSerialize( IFormatter& f );

There is a serialization library soon to be part of boost at www.boost.org.
The pre-release version can be found at www.rrsd.com. It melds the
(De)Serialize function pair into a single templated serialize function. So
you add a single template method to you class like:

template< class Archive > void serialize( Archive& ar, const int
version )
{
ar & mData1;
ar & mData2;

...
}

The method is then instantiated for input/output archives, which by the way
can be text,binary,xml or you can create your own custom archive class.
they contain some code to write to or read Data from formatter.

They also invoke the serialization of the base class. But any managemant
and tracking of object sized in the stream is done in the formatter.
That means, the base class serializer cannot be called directly. It
would just put its data into the stream without marking the end of the
base. This will result in the fact that I cannot add serialized
veriables to the base after the release of the program, or I would have
to write converter to read and write to documents from an older version.
And the older version cannot read docs from the newer one.

boost::serialization provides versioning. Just add to the above method:

if( version > 1 )
{
ar & mDataVersion2;

...
}

So what I want to do is passing a member function pointer into the
stream that points to the serializer of the base class.
Hmm, sounds weird? Ideas are welcome.

Prepend the member serialization with:

ar & boost::serialization::base_object said:
So, my problem regarding the function pointer is, that the functions are
virtual. They have to be virtual, because if the object is serialized
through a base class (this)pointer, I want the complete object to be
serialized.

boost::serialization does not use/require a common base class for
serialization, thereby avoids this problem.


Jeff Flinn
 
A

Andrey Tarasevich

Ben said:
...
If standard does not make a guarantee here, I'll have to assume that
it is NOT safe for a roundtrip between any two PTMs, generally
speaking.

The standard _does_ make a guarantee here. The explicit guarantee is
given, once again, in 5.2.10/9.
 
D

Don Clugston

My experience with VC is limited to VC6 and I don't know how much it is
different from VC7 in this respect. VC6 happens to support several PMF
models. The default one is called "Best-case always", which means that
VC6 is trying to optimize the size of PMFs by analyzing the class
hierarchy and choosing the size of the PMF accordingly. The logic it
uses is simple: if there's no multiple inheritance in the given class
and any of its ancestors, then the only thing PMFs to member functions
of this class need to store is the function's entry point (4 bytes
long). No additional information is needed. Otherwise (i.e. there's
multiple inheritance somewhere above in the hierarchy) VC6 would use
fully functional PMFs (8 bytes long).

It is easy to demonstrate that this VC6's logic is flawed and will lead
to non-standard conforming behavior in certain contexts (my original
example is one of such contexts, 'reinterpret_cast' round-trip is
another, just to name a few). However, there are a couple of compiler
options that will force VC6 to always use
multiple-inheritance-compatible PMFs regardless of the inheritance
structure (see '/vmg', '/vmm').

I'm pretty sure (although I could be wrong) that what you observe in
your experiment with VC7 happens for similar reasons. The source and
destination pointer types are so different internally that even
'reinterpret_cast' refuses to convert one into another. Maybe you should
check the compiler's documentation and see whether if describes some
options similar to '/vm...' in VC6.

I have recently written an article on this topic. There have been many
attempts to 'hack' closure pointers into C++; most fail because of an
inadequate understanding of the implementation of member function
pointers.


My article documents exactly what popular compilers are doing. I also
provide an implementation that satisfies Ben's original goal: closure
pointers without the use of new. I also provide operator == and !=, a
significant limitation of boost::function.

http://www.codeproject.com/cpp/FastDelegate.asp

Unfortunately, for MSVC and Intel C++, it's only possible with a nasty
hack. The portability is quite good: it works on MSVC 6, 7, 7.1, Gnu
3.2, and the latest versions of Intel C++ for Windows, Intel C++ for
Itanium, Digital Mars C++, Metrowerks (x86). It's also been compiled
on Comeau C++. Recently I've also got it working on BCB5.5, but that
version is not yet uploaded to the website.

There are a few errors in the article which I need to correct, but I
think you'll still find it an interesting read. It has *much* more
information than I've seen anywhere else.

Incidentally, if you use the /vmg option on VC6 (without specifying
/vmm or /vms), the compiler generates incorrect code!! In the virtual
inheritance case, it can end up calling the wrong function... Only
happens in fairly obscure circumstances, but it's an appalling bug.
They fixed this for VC7, but I haven't seen it mentioned anywhere.

-Don.
 
B

Bob Hairgrove

I

Ingo Nolden

Am Mon, 14 Jun 2004 08:57:36 -0400 schrieb Jeff Flinn <[email protected]>:

Thank you for responding. I like your proposal. There are things similar
to what I began. Others are less important. But I also have some questions:

To 3: My serialization can load newer files in older versions. I made bad
experience with serialization that could do only the other way.
I am doing this by saving the size of the object ( I do this for
inheritance levels also, so I can add members to a base class also. If an
inheritance Level is small I can still omit this. ) When deserializing a
newer file, any data after the members known in the old versions are
omitted and the formatter jumps to the point after the current object.
Deserializing an older file, standard values replace any missing data,
when the end of an onject is reached in the stream but still values are
demanded.

The syntax in your terms:

ar & SObject( pmData1, "mData1", pData1default );

I also thought about joining de- and serialization methods. I was just a
matter of blindness :)

To 4: I don't want to save objects twice, so if I am the owner of an
object I do the above, but when I just point to an object owned by someone
else I do:

ar & SRef( pmData1, "mData1", pData1default );

The base should be done like:

ar & SBase( ... ) <- here I was passing a function pointer, now it is open
what I'll do, I also want to be able to save the base as "inline" and omit
the size of the base and therefore I cannot change the base class'
serialization later. Maybe good for small bases.

Also important for me is references to objects that are not yet serialized
when serializing or not yet deserialized when deserializing. I keep a hash
map of pointers and stream positions to resolve them at the end of the
serialization.

Oh, I jast saw that Point 5 is probably addressing this issue.

To 11:

how is this useful manner of xml serialization. I have two requirements
only: I want to control wether something is a node or an attribute. And I
want to control the name for the attributes and nodes. I cannot see this
in your code examples in your posting, but I assume that I will find it in
the library docs that I am going to read this evening.


template< class Archive > void serialize( Archive& ar, const int
version )
{
ar & mData1;
ar & mData2;

...
}

Prepend the member serialization with:



boost::serialization does not use/require a common base class for
serialization, thereby avoids this problem.

Hmm, there`s something I did not understand. I guess the serialization
method can be virtual. Otherwise I couldn't pass an arbitrary Object, that
I point to via ObjectBase*, to the serializing mechanism. If the method is
virtual, do you make sure, when calling the serialize method, that the
version is called that belongs to the base class and not to the complete
object's class?

It seems this library is cloase to what I have/what I want. If it fits and
works well I will definitely need it. So, thank you for pointing me to it.

cheers

Ingo
 
J

Jeff Flinn

Ingo Nolden said:
Am Mon, 14 Jun 2004 08:57:36 -0400 schrieb Jeff Flinn
Thank you for responding. I like your proposal. There are things similar

Woe, It's Robert Ramey's proposal not mine. I just use it( with great
pleasure).
to what I began. Others are less important. But I also have some questions:

To 3: My serialization can load newer files in older versions. I made bad

I've never come across a product that is forward-compatible before, or at
least was visibly so. When you operate on the data loaded with an older
version of the software, and then serialize, don't you lose information?
experience with serialization that could do only the other way.
I am doing this by saving the size of the object ( I do this for
inheritance levels also, so I can add members to a base class also. If an
inheritance Level is small I can still omit this. ) When deserializing a
newer file, any data after the members known in the old versions are
omitted and the formatter jumps to the point after the current object.

You'd have to bring this up with Robert. You can join the boost devel
mailing list - via Yahoo Groups. I suppose you could derive your own archive
class that could accomodate this requirement.
Deserializing an older file, standard values replace any missing data,
when the end of an onject is reached in the stream but still values are
demanded.

This is how boost::serialization works but it's explicit with the version
info stored in the file.
The syntax in your terms:

ar & SObject( pmData1, "mData1", pData1default );

I also thought about joining de- and serialization methods. I was just a
matter of blindness :)

To 4: I don't want to save objects twice, so if I am the owner of an
object I do the above, but when I just point to an object owned by someone
else I do:

ar & SRef( pmData1, "mData1", pData1default );

The base should be done like:

ar & SBase( ... ) <- here I was passing a function pointer, now it is open
what I'll do, I also want to be able to save the base as "inline" and omit
the size of the base and therefore I cannot change the base class'
serialization later. Maybe good for small bases.

Also important for me is references to objects that are not yet serialized
when serializing or not yet deserialized when deserializing. I keep a hash
map of pointers and stream positions to resolve them at the end of the
serialization.

Oh, I jast saw that Point 5 is probably addressing this issue.

I think so.
To 11:

how is this useful manner of xml serialization. I have two requirements
only: I want to control wether something is a node or an attribute. And I
want to control the name for the attributes and nodes. I cannot see this
in your code examples in your posting, but I assume that I will find it in
the library docs that I am going to read this evening.




Hmm, there`s something I did not understand. I guess the serialization
method can be virtual. Otherwise I couldn't pass an arbitrary Object, that

No ClassBeingSerialized::serialize is not virtual.
I point to via ObjectBase*, to the serializing mechanism. If the method is
virtual, do you make sure, when calling the serialize method, that the
version is called that belongs to the base class and not to the complete
object's class?

See "Pointers to Objects of derived Classes" under "Special Considerations"
in the documentation. Generally the library handles these issues
automatically. In cases not automaticlaly handled, you register your classes
using:

#include <boost/serialization/export.hpp>
...
BOOST_CLASS_EXPORT_GUID(derived_one, "derived_one")
BOOST_CLASS_EXPORT_GUID(derived_two, "derived_two")


It seems this library is cloase to what I have/what I want. If it fits and
works well I will definitely need it. So, thank you for pointing me to it.

Your welcome, I hope it helps.

Jeff Flinn
 
B

Ben

I have recently written an article on this topic. There have been many
attempts to 'hack' closure pointers into C++; most fail because of an
inadequate understanding of the implementation of member function
pointers.

-Don.

Don, I did not go through the gory details of the implementation, but
I love this article. Will recommend it to my friends.

I totally agree with you in that 'delegate' is a much more
preferable,type-safer and simpler solution than PTM. And it is more
OO.

One question about Ryazanov's code (that was my another solution tried
before posting here, very appealing indeed.), is it standard C++ to
have PTM as a template parameter? I'm not a language lawyer, but after
searching the internet the only thing found that can be used as
non-type parameter was "integral type". And don't think PTM is
integral type, is it?

Finally, I may not dare to use your code in production. I tend to
avoid all gray areas whenever I can (the standard but non-portable
reinterpret_cast of PMF is another gray area that I will stay away
from).

My current solution would be: either my plain wrapper function where I
gain efficiency, or boost:function where I pay some runtime overhead
but gain functor support.


Just hope as you said, 'delegate' can be included in the next release
of the language.


Best Regards,
 
D

Don Clugston

Don, I did not go through the gory details of the implementation, but
I love this article. Will recommend it to my friends.

I totally agree with you in that 'delegate' is a much more
preferable,type-safer and simpler solution than PTM. And it is more
OO.

I think that PTM is horribly broken. Allowing you to specify an
overridden function from the derived class instead of through the base
class was IMHO a tragic mistake. That tiny bit of functionality came
at an enormous cost.
One question about Ryazanov's code (that was my another solution tried
before posting here, very appealing indeed.), is it standard C++ to
have PTM as a template parameter? I'm not a language lawyer, but after
searching the internet the only thing found that can be used as
non-type parameter was "integral type". And don't think PTM is
integral type, is it?

It is standard, but a bit wierd. PTM is not integral type. Instead,
it's a *name-based* template parameter, quite distinct from normal
parameters and from integral non-type parameters. The syntax is ugly,
so I'm not sure what theyintened it to be used for. Compiler support
for it is patchy, but improving. I would class it as a light gray
area.
Unfortunately, because of its behaviour with inline functions, you
can't use operator == which is a real shame.
Finally, I may not dare to use your code in production. I tend to
avoid all gray areas whenever I can (the standard but non-portable
reinterpret_cast of PMF is another gray area that I will stay away
from).

The relationship between "standard" and "portable" is interesting...
the surpising thing about my code is that it's the allegedly standard
part that requires a hack; the non-standard part (invoking the
function once it's been cast) is no problem at all.
My current solution would be: either my plain wrapper function where I
gain efficiency, or boost:function where I pay some runtime overhead
but gain functor support.


Just hope as you said, 'delegate' can be included in the next release
of the language.

I'm a little concerned that since they've included boost::function,
they may think they've answered the need for delegates/closures. But
really, boost::function generates slow, complicated code for such a
simple concept. I find it very disappointing. And the absence of
operator == is a real limitation which convolutes a lot of code.

-Don.
 

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,800
Messages
2,569,655
Members
45,387
Latest member
Bruna
Top