In C++, can member function pointers be used to point to derived (or even base) class members?
EDIT:
Perhaps an example will help. Suppose we have a hierarchy of three classes X, Y, Z in order of inheritance.
Y therefore has a base class X and a derived class Z.
Now we can define a member function pointer p for class Y. This is written as:
void (Y::*p)();
(For simplicity, I'll assume we're only interested in functions with the signature void f() )
This pointer p can now be used to point to member functions of class Y.
This question (two questions, really) is then:
Can p be used to point to a function in the derived class Z?
Can p be used to point to a function in the base class X?
C++03 std, §4.11 2 Pointer to member conversions:
An rvalue of type “pointer to member of B of type cv T,” where B is a class type, can be converted to an rvalue of type “pointer to member of D of type cv T,” where D is a derived class (clause 10) of B. If B is an inaccessible (clause 11), ambiguous (10.2) or virtual (10.1) base class of D, a program that necessitates this conversion is ill-formed. The result of the conversion refers to the same member as the pointer to member before the conversion took place, but it refers to the base class member as if it were a member of the derived class. The result refers to the member in D’s instance of B. Since the result has type “pointer to member of D of type cv T,” it can be dereferenced with a D object. The result is the same as if the pointer to member of B were dereferenced with the B sub-object of D. The null member pointer value is converted to the null member pointer value of the destination type. 52)
52)The rule for conversion of pointers to members (from pointer to member of base to pointer to member of derived) appears inverted compared to the rule for pointers to objects (from pointer to derived to pointer to base) (4.10, clause 10). This inversion is necessary to ensure type safety. Note that a pointer to member is not a pointer to object or a pointer to function and the rules for conversions of such pointers do not apply to pointers to members. In particular, a pointer to member cannot be converted to a void*.
In short, you can convert a pointer to a member of an accessible, non-virtual base class to a pointer to a member of a derived class as long as the member isn't ambiguous.
class A {
public:
void foo();
};
class B : public A {};
class C {
public:
void bar();
};
class D {
public:
void baz();
};
class E : public A, public B, private C, public virtual D {
public:
typedef void (E::*member)();
};
class F:public E {
public:
void bam();
};
...
int main() {
E::member mbr;
mbr = &A::foo; // invalid: ambiguous; E's A or B's A?
mbr = &C::bar; // invalid: C is private
mbr = &D::baz; // invalid: D is virtual
mbr = &F::bam; // invalid: conversion isn't defined by the standard
...
Conversion in the other direction (via static_cast) is governed by § 5.2.9 9:
An rvalue of type "pointer to member of D of type cv1 T" can be converted to an rvalue of type "pointer to member of B of type cv2 T", where B is a base class (clause 10 class.derived) of D, if a valid standard conversion from "pointer to member of B of type T" to "pointer to member of D of type T" exists (4.11 conv.mem), and cv2 is the same cv-qualification as, or greater cv-qualification than, cv1.11) The null member pointer value (4.11 conv.mem) is converted to the null member pointer value of the destination type. If class B contains the original member, or is a base or derived class of the class containing the original member, the resulting pointer to member points to the original member. Otherwise, the result of the cast is undefined. [Note: although class B need not contain the original member, the dynamic type of the object on which the pointer to member is dereferenced must contain the original member; see 5.5 expr.mptr.oper.]
11) Function types (including those used in pointer to member function
types) are never cv-qualified; see 8.3.5 dcl.fct.
In short, you can convert from a derived D::* to a base B::* if you can convert from a B::* to a D::*, though you can only use the B::* on objects that are of type D or are descended from D.
I'm not 100% sure what you are asking, but here is an example that works with virtual functions:
#include <iostream>
using namespace std;
class A {
public:
virtual void foo() { cout << "A::foo\n"; }
};
class B : public A {
public:
virtual void foo() { cout << "B::foo\n"; }
};
int main()
{
void (A::*bar)() = &A::foo;
(A().*bar)();
(B().*bar)();
return 0;
}
The critical issue with pointers to members is that they can be applied to any reference or pointer to a class of the correct type. This means that because Z is derived from Y a pointer (or reference) of type pointer (or reference) to Y may actually point (or refer) to the base class sub-object of Z or any other class derived from Y.
void (Y::*p)() = &Z::z_fn; // illegal
This means that anything assigned to a pointer to member of Y must actually work with any Y. If it was allowed to point to a member of Z (that wasn't a member of Y) then it would be possible to call a member function of Z on some thing that wasn't actually a Z.
On the other hand, any pointer to member of Y also points the member of Z (inheritance means that Z has all the attributes and methods of its base) is it is legal to convert a pointer to member of Y to a pointer to member of Z. This is inherently safe.
void (Y::*p)() = &Y::y_fn;
void (Z::*q)() = p; // legal and safe
You might want to check out this article Member Function Pointers and the Fastest Possible C++ Delegates The short answer seems to be yes, in some cases.
I believe so. Since the function pointer uses the signature to identify itself, the base/derived behavior would rely on whatever object you called it on.
My experimentation revealed the following: Warning - this might be undefined behaviour. It would be helpful if someone could provide a definitive reference.
This worked, but required a cast when assigning the derived member function to p.
This also worked, but required extra casts when dereferencing p.
If we're feeling really ambitious we could ask if p can be used to point to member functions of unrelated classes. I didn't try it, but the FastDelegate page linked in dagorym's answer suggests it's possible.
In conclusion, I'll try to avoid using member function pointers in this way. Passages like the following don't inspire confidence:
Casting between member function
pointers is an extremely murky area.
During the standardization of C++,
there was a lot of discussion about
whether you should be able to cast a
member function pointer from one class
to a member function pointer of a base
or derived class, and whether you
could cast between unrelated classes.
By the time the standards committee
made up their mind, different compiler
vendors had already made
implementation decisions which had
locked them into different answers to
these questions. [FastDelegate article]
Assume that we have class X, class Y : public X, and class Z : public Y
You should be able to assign methods for both X, Y to pointers of type void (Y::*p)() but not methods for Z. To see why consider the following:
void (Y::*p)() = &Z::func; // we pretend this is legal
Y * y = new Y; // clearly legal
(y->*p)(); // okay, follows the rules, but what would this mean?
By allowing that assignment we permit the invocation of a method for Z on a Y object which could lead to who knows what. You can make it all work by casting the pointers but that is not safe or guaranteed to work.
Here is an example of what works.
You can override a method in derived class, and another method of base class that uses pointer to this overridden method indeed calls the derived class's method.
#include <iostream>
#include <string>
using namespace std;
class A {
public:
virtual void traverse(string arg) {
find(&A::visit, arg);
}
protected:
virtual void find(void (A::*method)(string arg), string arg) {
(this->*method)(arg);
}
virtual void visit(string arg) {
cout << "A::visit, arg:" << arg << endl;
}
};
class B : public A {
protected:
virtual void visit(string arg) {
cout << "B::visit, arg:" << arg << endl;
}
};
int main()
{
A a;
B b;
a.traverse("one");
b.traverse("two");
return 0;
}
Related
I see some strange code in our project like follows.I test it and get the right answer.But I think it is illegal,Can anyone explain this to me?
class Member
{
public:
Member():
a(0),b(1)
{}
int a;
int b;
};
// contains `Member` as its first member
class Container
{
public:
Container():
c(0),d(0)
{}
Member getMemb(){return fooObject;}
Member fooObject;
int c;
int d;
};
and how we use it:
int main()
{
auto ctain = new Container;
auto meb = (Member *)ctain; // here! I think this is illegal
cout << "a is " << meb->a << ", b is" << meb->b << endl;
return 0;
}
but I get the right answer, a is 0 and b is 1.Is this just a coincidence?I also noted that if fooObject is not the first member, I will get a wrong answser.
The snippet is legal. The C style cast (Member*) here is effectively a reinterpret_cast. From [basic.compound]
Two objects a and b are pointer-interconvertible if:
they are the same object, or
one is a union object and the other is a non-static data member of that object, or
one is a standard-layout class object and the other is the first non-static data member of that object, or, if the object has no non-static data members, the first base class subobject of that object, or [...]
If two objects are pointer-interconvertible, then they have the same address, and it is possible to obtain a pointer to one from a pointer to the other via a reinterpret_cast.
Special care should be taken to make sure it is indeed a standard layout type, possibly with a static_assert(std::is_standard_layout_v<Container>)
On the other hand, you could sidestep this entire fiasco if you just wrote auto meb = &ctain.fooObject;
It's not exactly coincidence, it happens that your fooObject is the first member of your Container class, so the beginning of it will rest at the same starting address as the Container object. If you do:
size_t s = offsetof(Container, Container::fooObject);
It will tell that your fooObject offset will be 0, which start where your Container object start in terms of memory, so when you cast to a Member pointer it's pointing to the correct address. But for instance in other cases you would be in big trouble for sure:
class Container
{
public:
Container() : c(0),d(0) {}
Member getMemb(){return fooObject;}
int c; // fooObject isn't first member
Member fooObject;
int d;
};
Or was a virtual class, because virtual classes store an pointer for lookup into a table.
class Container
{
public:
Container() : c(0),d(0) {}
virtual ~Container() {} // Container is virtual, and has a vtable pointer
// Meaning fooObject's offset into this class
// most likely isn't 0
Member getMemb(){return fooObject;}
Member fooObject;
int c;
int d;
};
Someone else will have to tell you whether this cast is legal even in your example, because I'm not sure.
C++ Standard in part 12.2 Class members:
If a standard-layout class object has any non-static data members, its address is the same as the address of its first non-static data member. Otherwise, its address is the same as the address of its first base class subobject (if any).
Both your classes have standard-layout. So, observed behaviour agrees with Standard.
But the cast auto meb = (Member *)ctain; breaks strict aliasing rule.
I'm working in some code using the Curiously Recurring Template Pattern, where the derived class is passed up to specialize its own base classes at compile time.
With this, I run into an annoying problem which I have made a toy example for. I have annotated base::bar with comments describing the expected behavior (and why I expect it). The entire example is intended to be compiled as is, and results in 3 compiler errors (clang-3.9) as noted in the comments. For reference, $4.11/2:
A prvalue of type “pointer to member of B of type cv T”, where B is a class type, can be converted to a
prvalue of type “pointer to member of D of type cv T”, where D is a derived class (Clause 10) of B. If B is
an inaccessible (Clause 11), ambiguous (10.2), or virtual (10.1) base class of D, or a base class of a virtual
base class of D, a program that necessitates this conversion is ill-formed. The result of the conversion refers
to the same member as the pointer to member before the conversion took place, but it refers to the base
class member as if it were a member of the derived class. The result refers to the member in D’s instance of B. Since the result has type “pointer to member of D of type cv T”, it can be dereferenced with a D object.
The result is the same as if the pointer to member of B were dereferenced with the B subobject of D. The
null member pointer value is converted to the null member pointer value of the destination type.57
#include <type_traits>
template<typename impl_t>
struct super
{
typedef int impl_t::* member_dat_t;
protected:
template<member_dat_t dat>
int foo()
{
return static_cast<impl_t *>(this)->*dat;
}
};
template<typename impl_t>
struct base : super<impl_t>
{
using this_t = base<impl_t>;
int base_dat = 0;
int bar()
{
// This, of course, succeeds
this-> template foo<&impl_t::derived_dat>();
// This fails during template instantiation, because the compiler knows that the
// location of base_dat/base_func is in the base<derived> subobject of a derived
// object rather than derived itself.
this-> template foo<&impl_t::base_dat>();
// (i.e., this succeeds, despite the address being taken on a member resolved off the impl_t)
static_assert(std::is_same<int this_t::*, decltype((&impl_t::base_dat))>::value, "");
// But what if we cast, as the standard (N3242) permits in $4.11/2 [conv.mem]
// Now, these succeed
static_assert(std::is_same<int impl_t::*, decltype((static_cast<int impl_t::*>(&impl_t::base_dat)))>::value, "");
static_assert(std::is_same<typename super<impl_t>::member_dat_t, decltype((static_cast<int impl_t::*>(&impl_t::base_dat)))>::value, "");
// But these still fail
this-> template foo<static_cast<int impl_t::*>(&impl_t::base_dat)>();
this-> template foo<static_cast<typename super<impl_t>::member_dat_t>(&impl_t::base_dat)>();
return 1;
}
};
struct derived : base<derived>
{
int derived_dat;
};
void test()
{
derived d;
d.bar();
}
For the "why": the existing code in base only ever instantiates super::foo with members actually defined in derived. It would please me if I could also use members defined in base<derived>, inherited by derived. The actual code is using member functions, but member data sufficed for the example.
Edit:
I've technically come up with a solution that should work. Basically, base derives from super<base<impl_t>> instead of super<impl_t>, so now we can invoke foo with members of base<impl_t>, and if we need different behavior from the most-derived classes, we can make the methods in base virtual. But this throws away a little of the benefit of CRTP (we now have VTBL in the objects, and the cost of dynamic polymorphism, even when we know at compile time what we want).
Thanks
I have an issue regarding access declarations under g++ (version 5.1).
class Base
{
public:
void doStuff() {}
};
class Derived : private Base
{
public:
// Using older access declaration (without using) shoots a warning
// and results in the same compilation error
using Base::doStuff;
};
template<class C, typename Func>
void exec(C *c, Func func)
{
(c->*func)();
}
int main()
{
Derived d;
// Until here, everything compiles fine
d.doStuff();
// For some reason, I can't access the function pointer
exec(&d,&Derived::doStuff);
}
g++ fails to compile the above code with:
test.cpp: In instantiation of ‘void exec(C*, Func) [with C = Derived; Func = void (Base::*)()]’:
test.cpp:24:27: required from here
test.cpp:17:4: error: ‘Base’ is an inaccessible base of ‘Derived’
(c->*func)();
Even when the function itself can be called (d.doStuff();) the pointer can't be used even though I declared the function as accessible from the outside.
Private inheritance is also important, to some extent, because the Derived class chooses to expose only a certain set of members from base(s) which are interface implementations IRL.
NB : this is a question about the language, not class design.
The problem is that &Derived::doStuff isn't actually a pointer to a member of class Derived. From [expr.unary.op]:
The result of the unary & operator is a pointer to its operand. The operand shall be an lvalue or a qualified-id.
If the operand is a qualified-id naming a non-static or variant member m of some class C with type T,
the result has type “pointer to member of class C of type T” and is a prvalue designating C::m.
doStuff is not a member of Derived. It is a member of Base. Hence it has type pointer to member of Base, or void (Base::*)(). What the using-declaration does here is simply an aid to overload resolution, from [namespace.udecl]:
For the purpose of overload resolution, the functions which are introduced by a using-declaration into a
derived class will be treated as though they were members of the derived class.
That's why d.doStuff() works. However, through the function pointer, you're trying to call a Base member function on a Derived object. There's no overload resolution here since you're using a function pointer directly, so the base class function would be inaccessible.
You might think you could just cast &Derived::doStuff to the "correct" type:
exec(&d, static_cast<void (Derived::*)()>(&Derived::doStuff));
But you can't do that either according to [conv.mem], since again Base is an inaccessible base of Derived:
A prvalue of type “pointer to member of B of type cv T”, where B is a class type, can be converted to a
prvalue of type “pointer to member of D of type cv T”, where D is a derived class (Clause 10) of B. If B is an
inaccessible (Clause 11), ambiguous (10.2), or virtual (10.1) base class of D, or a base class of a virtual base
class of D, a program that necessitates this conversion is ill-formed.
I guess the reason is that the member function is not really part of the derived class, but rather of the base class. This can be shown somehow empirically by inspecting the type of the member function pointer and comparing it with a pointer to the base member function:
cout << typeid(&Derived::doStuff).name() << endl
<< typeid(& Base::doStuff).name() << endl;
Live here.
I'm currently searching the standard for some background on this.
Barry's answer holds the respective parts of the standard.
According to the stardard [namespace.udecl]:
A using-declaration introduces a name into the declarative region in which the using-declaration appears.
If a using-declaration names a constructor (3.4.3.1), it implicitly
declares a set of constructors in the class in which the
using-declaration appears (12.9); otherwise the name specified in a
using-declaration is a synonym for a set of declarations in another
namespace or class.
So you're just introducing Base::doStuff into the Derived region, it's still a member function of Base.
Then exec is instantiated as exec<Derived, void (Base::*)()>, but it can't cast a Derived* to Base* because of the private inheritance.
From the C++11 Standard, §7.3.3 [namespace.udecl], 18:
class A
{
private:
void f( char );
public:
void f( int );
protected:
void g();
};
class B : public A
{
using A::f; // error: A::f(char) is inaccessible
public:
using A::g;
// B::g is a public synonym for A::g
};
Note the B::g is a public synonym for A::g part. When you take the address of Derived::doStuff, GCC is creating a pointer to member function of type void(Base::*)(), and the standard says it's doing well. So, I think the compile time error is fair.
I use the following code to access a protected member of an object.
class Base {
protected:
void foo();
};
class PublicBase : public Base {
public:
static void bar(Base *obj) {
static_assert(sizeof(PublicBase) == sizeof(Base), "Not today");
static_cast<PublicBase *>(obj)->foo();
}
};
Can I assume that provided code is safe to use (in theory and in practice)?
In theory, no. In practice, maybe, as long as there are no virtual functions involved. The relevant passage in the standard is 5.2.9 (2):
An lvalue of type "cv1 B", where B is a class type, can be cast to type "reference to cv2 D," where D is a class derived from B, if a valid standard conversion from "pointer to D" to "pointer to B" exists, cv2 is the same cv-qualification as, or greater cv-qualification than, cv1, and B is neither a virtual base class of D nor a base class of a virtual base class of D. The result has type "cv2 D." (...) If the object of type "cv1 B" is actually a subobject of an object of type D, the result refers to the enclosing object of type D. Otherwise, the result of the cast is undefined.
Emphasis mine.
EDIT: Well, my first approach at a dirty-but-legal hack didn't work, which is nice because the loophole I thought existed was one that shouldn't. There's no way to legally call foo on *obj that I can find -- as well there shouldn't be, if protected is to have any meaning at all -- so the best I can offer are two ideas for workarounds:
Firstly, if copying (or moving) back and forth is okay
class PublicBase : public Base {
public:
void bar(Base *obj) {
// upcast, perfectly legal. make a working copy into self
*static_cast<Base*>(this) = *obj;
// work
foo();
// copy back
*obj = *this;
}
};
Secondly, if you can derive from Base and use the new class whereever you would have used Base before:
// use this instead of Base everywhere.
class BaseWithAccess {
public:
void publicfoo() { foo(); }
};
Other than that, I have nothing. Unless you can change Base, in which case you could just make foo public.
Is it legal to cast a pointer to a method of derived class to a pointer to a method of base class, even though the base class does not declare any methods, especially of the "casted" method is called through an object of type base class, as follows:
// works in VS 2008 and g++ 4.5.3
struct Base
{
};
struct Fuu : public Base
{
void bar(){ std::cout << "Fuu::bar" << std::endl; }
void bax(){ std::cout << "Fuu::bax" << std::endl; }
};
struct Foo : public Base
{
void bar(){ std::cout << "Foo::bar" << std::endl; }
void bax(){ std::cout << "Foo::bax" << std::endl; }
};
typedef void (Base::*PtrToMethod)();
int main()
{
PtrToMethod ptr1 = (PtrToMethod) &Foo::bax;
PtrToMethod ptr2 = (PtrToMethod) &Fuu::bax;
Base *f1 = new Foo;
Base *f2 = new Fuu;
(f1->*ptr1)();
(f2->*ptr2)();
}
It's worth noting that the reason the target object is contravariant is because this is effectively a parameter being passed to the function, and parameters are, in theory, contravariant (if the function can use a Base*, it can be safely plugged into any algorithm which provides only Derived* as the actual arguments).
However, for arbitrary parameters, a shim may be needed to adjust the pointer, if the base subobject is not placed at the beginning of the derived class layout. With pointer-to-members, pointer adjustment for the this pointer is built into the language. (And for this reason, pointer-to-member-of-class-with-virtual-inheritance can get quite large)
No. This is described in section 4.11 of the standard (I have n3337.pdf draft):
A prvalue of type “pointer to member of B of type cv T”, where B is a
class type, can be converted to a prvalue of type “pointer to member
of D of type cv T”, where D is a derived class (Clause 10) of B. If B
is an inaccessible (Clause 11), ambiguous (10.2), or virtual (10.1)
base class of D, or a base class of a virtual base class of D, a
program that necessitates this conversion is ill-formed. The result of
the conversion refers to the same member as the pointer to member
before the conversion took place, but it refers to the base class
member as if it were a member of the derived class. The result refers
to the member in D’s instance of B. Since the result has type “pointer
to member of D of type cv T”, it can be dereferenced with a D object.
The result is the same as if the pointer to member of B were
dereferenced with the B subobject of D. The null member pointer value
is converted to the null member pointer value of the destination
type.57
In general pointer to member conversions work in the opposite direction then pointers to derived/base classes. Pointers to (sub)objects can be converted towards the base class and pointer to methods can be converted towards more derived classes.
Please note that your program is ill formed if you attempt to call through any of the above pointers when the object involved is not of type Foo/Fuu or their derivative.
While I believe your code takes the risk it looks like similar casting (even without inheritance involved) was heavily used in OWL 2.0 library from Borland at some point.
It is legal to cast the pointer. In order to use it, you must cast it back to its original type. The underlying problem is that the pointer-to-function points to a member of the derived class; there is no guarantee that that member is a member of the base class.