I'd like to have child classes register callbacks to their parent class so that users of the parent class can call methods of the child with a known function signature.
typedef int(*Func)(int);
class A
{
public:
void registerFunc(Func f)
{}
};
class B : public A
{
public:
B()
{
A::registerFunc(&B::myF);
}
int myF(int x) {
// do stuff with member variables
return 3;
}
};
But I get this compiler error
main.cpp:18:23: error: cannot initialize a parameter of type 'Func' (aka 'int (*)(int)') with an rvalue of type 'int (B::*)(int)'
A::registerFunc(&B::myF);
^~~~~~~
main.cpp:8:28: note: passing argument to parameter 'f' here
void registerFunc(Func f)
Here's a Repl illustrating the error in a concise example.
https://replit.com/@Carpetfizz/RudeSmoothComments#main.cpp
The accepted answer in a related thread suggested to override a virtual function declared in A
but my use case actually requires dynamic callback registrations.
CodePudding user response:
You can try this.
typedef std::function<int (int)> Func;
class A
{
public:
void registerFunc(Func f)
{}
};
class B : public A
{
public:
B()
{
A::registerFunc(std::bind(&B::myF, *this, std::placeholders::_1));
}
int myF(int x) {
// do stuff with member variables
return 3;
}
};
CodePudding user response:
If I understand the goal (and believe me, that's a sketchy 'if'), you want to specify some member of some A
derivation to invoke from some A
member as a dispatched 'callback' mechanic. If that is the case, then to answer your question in comment, yes, a function and bind can do this. It can even be semi-protected with a little help from sfinae:
Example
#include <iostream>
#include <type_traits>
#include <functional>
#include <memory>
struct A
{
virtual ~A() = default;
std::function<void(int)> callback = [](int){};
template<class Derived>
std::enable_if_t<std::is_base_of<A, Derived>::value>
registerCallback(void (Derived::*pfn)(int))
{
using namespace std::placeholders;
callback = std::bind(pfn, dynamic_cast<Derived*>(this), _1);
}
void fire(int arg)
{
callback(arg);
}
};
struct B : public A
{
void memberfn(int arg)
{
std::cout << __PRETTY_FUNCTION__ << ':' << arg << '\n';
}
};
struct Foo
{
void memberfn(int arg)
{
std::cout << __PRETTY_FUNCTION__ << ':' << arg << '\n';
}
};
int main()
{
std::unique_ptr<A> ptr = std::make_unique<B>();
ptr->registerCallback(&B::memberfn);
// ptr->registerCallback(&Foo::memberfn); // WILL NOT WORK
ptr->fire(42);
}
Output
void B::memberfn(int):42
The Parts
The first part is straight forward. We declare a member variable callback
to be a std::function<void(int)>
instance. This is where we'll eventually bind our callable object point. The default value is a lambda that does nothing.
The second part is... a little more complicated:
template<class Derived>
std::enable_if_t<std::is_base_of<A, Derived>::value>
registerCallback(void (Derived::*pfn)(int))
This declares registerCallback
as an available member function that accepts a non-static member function pointer taking one int
as an argument, but only if the class hosting that member function, or a derivative therein, is a derivation of A
(or A
itself). Some non-A
derivative Foo
with a member void foo(int)
will not compile.
Next, the setup to the callback itself.
using namespace std::placeholders;
callback = std::bind(pfn, dymamic_cast<Derived*>(this), _1);
This just binds the pointer-to-member to this
dynamic-cast to the derivation type (which had better work or we're in trouble, see final warning at the end of this diatribe), and sets the call-time placeholder. The _1
you see comes from the std::placeholders
namespace, and is used to delay providing an argument to the callback until such time as we actually invoke it (where it will be required,and you'll see that later). See std::placehholders
for more information.
Finally, the fire
member, which does this:
void fire(int arg)
{
callback(arg);
}
This invokes the registered function object with the provided argument. Both the member function and this
are already wired into the object. The argument arg
is used to fill in the placeholder we mentioned earlier.
The test driver for this is straightforward:
int main()
{
std::unique_ptr<A> ptr = std::make_unique<B>();
ptr->registerCallback(&B::memberfn);
// ptr->registerCallback(&Foo::memberfn); // WILL NOT WORK
ptr->fire(42);
}
This creates a new B
, hosting it in a dynamic A
pointer (so you know there is no funny business going on). Even with that, because B
derived from A
the registerCallback
sfinae filtering passes inspection and the callback is registered successfully. We then invoke the fire
method, passing our int
argument 42
, which will be sent to the callback, etc.
Warning: With great power comes great responsibility
Even those there is protection from passing non-A
derived member functions, there is absolutely none from the casting itself. It would be trivial to craft a basic A
, pass a B
member (which will work since A
is its base), but there is no B
actually present.
You can catch this at runtime via that dynamic_cast
, which we're currently not error checking. For example:
registerCallback(void (Derived::*pfn)(int))
{
using namespace std::placeholders;
Derived *p = dynamic_cast<Derived*>(this);
if (p)
callback = std::bind(pfn, p, _1);
}
You can choose the road more risky. Personally, i'd detect the null case and throw an exception just to be safe(er)