I need to use a member function pointer that takes in an argument of base class that used in other code. Well, simply I want do to [something] like the example below. This code works fine, but I wonder if such cast is always safe? I cannot do dynamic
or static
cast here.
#include <cstdio>
class C
{
public:
C () : c('c') {}
virtual ~C() {}
const char c;
};
class D : public C
{
public:
D () : d('d') {}
virtual ~D() {}
const char d;
};
class A
{
public:
A () {}
virtual ~A() {}
void f( C& c ) { printf("%c\n",c.c); }
void g( D& d ) { printf("%c %c\n",d.c,d.d); }
};
int main (int argc, char const* argv[])
{
void (A::*pf)( C& c ) = &A::f;
void (A::*pg)( D& d ) = reinterpret_cast<void (A::*)(D&)>(&A::f);
A a;
C c;
D d;
(a.*pf)( c );
(a.*pg)( d );
return 0;
}
What you are trying to do cannot be done legally in C++. C++ does not support any kind of co-variance or contra-variance on function parameter types, regardless of whether this is a member function or a free function.
In your situation the proper way to implement it is to introduce an intermediate function for parameter-type-conversion purposes
class A
{
public:
...
void f( C& c ) { printf("%c\n",c.c); }
void f_with_D( D& d ) { f(d); }
...
};
and make your pointer point to that intermediate function without any casts
void (A::*pg)( D& d ) = &A::f_with_D;
Now
A a;
D d;
(a.*pg)( d );
will ultimately call a.f
with C
subobject of object d
as argument.
EDIT: Yes, it will work with function overload (if I understand your question correctly). You just have to keep in mind that with function overload in order to direct the inner call to the proper version of the function you'll have to use an explicit cast
class A
{
public:
...
void f( C& c ) { printf("%c\n",c.c); }
void f( D& d ) { f(static_cast<C&>(d)); }
...
};
Without the cast you'll end up with A::f(D&)
calling itself recursively.
No, your example does not work fine.
First, you can only use dynamic_cast
to cast between related class-types, not something else.
Second, even if you replace that dynamic_cast
with a reinterpret_cast
or C-style cast (which I assume you meant), then I get the following output:
c
c
Not really what you wanted.
Why that even works and doesn't horribly crash is because it is "safe" to cast back and forth between member-function-pointers, no information will be lost.
Why it still prints something is because the compiler sees no errors with the types, but the assembly doesn't care about types, it only cares about addresses, so it will still call A::f
, because that is the pointer you saved, regardless of the type.
Interestingly, this still works even if you unrelate the classes (D
doesn't inherit from C
), again because assembly doesn't care about types. Changing the functions in A in the following way:
void f( C& c ) { printf("f(C& c): %c\n",c.c); }
void g( D& d ) { printf("g(D& d): %c\n",d.d); }
leads to the following output:
f(C& c): c
f(C& c): d
"How does that work? D
doesn't even have a c
member!". Well, again because of addresses. Both variables are at the same offset from the this
pointer, namely +0
. Now lets put another member in C
(class simplified):
struct C{
C () : c('c') {}
int i; // mean
const char c;
};
And try that again, output:
f(C& c): c
f(C& c): ╠
Yep, there we go. C::c
is now at the offset +4
(+0
+ sizeof int
), and printf
reads from there. In D
, there is no such offset and printf
reads from uninitialized memory. Accessing uninitialized memory, on the other hand, is undefined behaviour.
So, to finally come to the conclusion: No, this is not safe. :)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With