Home > Software design >  Passing member method of derived class as base class method pointer
Passing member method of derived class as base class method pointer

Time:09-23

Can I pass the method as a pointer to some function that accepts the base class method pointer? Like here, function tryit accepts two parameters with class Object. There should be polymorphism, but the compiler throws an error.

#include <iostream>

using namespace std;

class Object {
};


class Derived : public Object
{
private:
    
public:
    void printit() {
        cout << "Ok" << endl;
    }
};




void tryit(Object* obj, void (Object::*fn)()  ) {
    (obj->*fn)();
}


int main() {
    Derived d;
    tryit(&d, &Derived::printit);
}

Compiler says this:

main.cc: In function ‘int main()’:
main.cc:31:15: error: cannot convert ‘void (Derived::*)()’ to ‘void (Object::*)()’
   31 |     tryit(&d, &Derived::printit);
      |               ^~~~~~~~~~~~~~~~~
      |               |
      |               void (Derived::*)()
main.cc:24:25: note:   initializing argument 2 of ‘void tryit(Object*, void (Object::*)())’
   24 | void tryit(Object* obj, void (Object::*fn)()  ) {
      |                         ^~~~~~~~~~~~~~~~~~~~

I don't want to use virtual methods in Object class, because I want to be able to call function with various names.

This works:

typedef void (Object::*memfn)();
tryit(&d, (memfn) &Derived::printit);

But why this is not converted implicitly, why do I need to cast it manually?

CodePudding user response:

Unfortunately, polymorphism doesn't work this way. Member-pointers of derived classes are not implicitly convertible to member-pointers of parent classes. Only pointers (and references) to derived class objects are implicitly convertible to pointers to parent class objects.

You can cast your pointer, and make compiler happy:

int main() {
    Derived d;
    tryit(&d, static_cast<void (Object::*)()>(&Derived::printit));
}

Thanks to @StoryTeller-UnslanderMonica for digging, there seems to be an explicit blessing in Standard:

https://timsong-cpp.github.io/cppwp/n4868/expr.static.cast#12

CodePudding user response:

Using virtual is the legal and safe way to handle this for polymorphic types. Your claim that you don't want to use virtual because you "want to be able to call function with various names" makes no sense.

But, if you really don't want to use virtual then consider making tryit() a template function instead, eg:

template<typename T>
void tryit(T* obj, void (T::*fn)() ) {
    (obj->*fn)();
}

int main() {
    Derived d;
    tryit(&d, &Derived::printit);
}

Alternatively:

template<typename Callable>
void tryit(Callable fn) {
    fn();
}

int main() {
    Derived d;
    tryit([&](){ d.printit(); });
}

Or, you can use std::function without a template, eg:

void tryit(std::function<void()> fn) {
    fn(); 
}

int main() {
    Derived d;
    tryit([&](){ d.printit(); });
}

CodePudding user response:

But why this is not converted implicitly, why do I need to cast it manually?

Because it's one of those conversions where you have to tell the compiler you posses extra knowledge that guarantees it's safe. Take object pointers for instance:

struct A { int x; };
struct B : A { char c; };

A *pa = new B();
auto pb = static_cast<B*>(pa);

Converting a B* to an A* is implicit. It's an unambiguous base class. The compiler knows there is an A object in that B and can just go ahead with it. But the converse is not true, you must cast it (employing your extra knowledge) to let it know that that A* is really pointing at a B*.

Pointers to members are the same in a way.

int B::* pmb = &A::x;
auto pma = static_cast<char A::*>(&B::c);
pa->*pma = 'c';

Obtaining a pointer to a member of B from a pointer to a member of A is an implicit conversion. The same knowledge about B containing an A (and therefore the member x) is available to the compiler. But it cannot assume the converse willy-nilly. What if the object pointer pa is not really pointing at a B? Accessing that "member of B" would be disastrous then.

By the same reasoning as before, you need a cast to let the compiler know you have extra knowledge about the actual derived object type.

  • Related