I'm just porting some code from javascript to C . As some might know, in JS it's pretty ususal to buffer callbacks in a vector, but how can I do the same in C ?
Consider the following - not yet working - code. What I'm doing here is for an object tree
to register a callback with its leaf
(sorry couldn't think of better names). As the callback function will access members within tree
itself, it needs to capture the this-pointer. The problem that arises now is twofold:
The leaf class layout needs to be known, therefore I need to provide the type of the callable to the vector. But it's impossible to know the type of a lambda beforehand, especially if it catches the this ptr.
Even if the type could be deduced beforehand, I still have the problem that the lambda type would probably only allow this-pointers of a specific object to be embedded into its type, thus rendering every call to
register_callback()
that doesn't originate fromtree
as unavailable.
#include <vector>
#include <cstdio>
template <std::invocable Cb>
class leaf
{
public:
auto register_callback(Cb)
{
}
auto do_leave()
{
for (auto cb : callbacks_) {
cb();
}
}
std::vector<Cb> callbacks_;
};
class tree
{
public:
tree()
{
myleaf_.register_callback([this](){
do_some_messageing();
});
}
auto do_some_messageing() -> void
{
printf("Hello World\n");
}
leaf<???> myleaf_;
};
int main()
{
tree Tree1;
Tree1.myleaf_.do_leave();
}
What would I have to do to circumvent those problems? If possible without std::function. I'm also open for different approaches.
CodePudding user response:
If you really want to avoid using std::function (I don't understand why, but it might improve debuggability?) you could simply implement a own function handler. Then you could make virtual functions do the heavy lifting.
If you dont like when the leaf handles pointers, you could hide the pointer magick inside some wrapper class.
If you dont want heap allocated object, you could have the memory allocated in your callback type and "allocate" memory on your reserved space.
std::function
is easiest, but there is plenty of other alternatives.
#include <vector>
#include <cstdio>
#include <memory>
class callback_base {
public:
virtual void operator() () = 0;
virtual ~callback_base() = default;
};
template <typename Cb>
struct callback: public callback_base {
callback(Cb cb): _callback{cb} {}
Cb _callback;
void operator() () override {
_callback();
}
};
class leaf
{
public:
template <typename Cb>
auto register_callback(Cb cb)
{
callbacks_.push_back(std::make_unique<callback<Cb>>(cb));
}
auto do_leave()
{
for (auto &cb : callbacks_) {
(*cb)();
}
}
std::vector<std::unique_ptr<callback_base>> callbacks_;
};
class tree
{
public:
tree()
{
myleaf_.register_callback([this](){
do_some_messageing();
});
}
auto do_some_messageing() -> void
{
printf("Hello World\n");
}
leaf myleaf_;
};
int main()
{
tree Tree1;
Tree1.myleaf_.do_leave();
}
CodePudding user response:
As you figured, templates in Cpp require everything to be deducible at compile time because they effectively work similar to plain replacements. The compiled code contains definitions of all actually usable variants of the template. As a result of that, there will be separate definitions of the invocables and you cannot mix them into the same tree. "Abstracting" this entirely with wrapped function pointers - aka std::function is likely the best solution. You can wrap those further if you do not want to expose such a "free for all" input to the user of your class.