1

I'm using a static class called websocket which listens on a websocket (hence the name) and calls a callback function once data is received. I created the following declarations

typedef int (*cb)(Json::Value &json);

static void websocket::init();
static void websocket::listen(cb callback, const char* address);

I also have a class called app which should contain the callback as a member function, because it is using a member property connecting to the database:

int app::callback(Json::Value& json)
{
    this->db->insert(json);
};

I'm calling the websocket::listen() function inside app like this:

void app::start()
{
    websocket::listen(std::mem_fn(&app::callback), this->path);
}

I get an error saying error: cannot convert 'std::_Mem_fn<int (app::*)(Json::Value&)>' to 'cb' {aka 'int (*)(Json::Value&)'}. To be able to keep the code as generic as possible so that I can call websocket::listen(); with other callbacks as well, I don't want to change the cb typedef. I can also not make app::callback static as it requires this->db. What is the best way to deal with this problem?

laurensvm
  • 143
  • 8
  • 2
    Given the restrictions you have placed upon yourself there is no solution. You are going to have to compromise somewhere. – john Feb 19 '21 at 09:51
  • 2
    The simplest compromise would be to change the typedef to use [std::function](https://en.cppreference.com/w/cpp/utility/functional/function) `typedef std::function cb;` – john Feb 19 '21 at 09:53
  • I'm struggling a bit with using std::function as the signature. How would I call it from `app::start`? – laurensvm Feb 19 '21 at 09:58
  • Also, is there a design pattern to solve this problem in C++? Should I try to make the db static and not a property of app? – laurensvm Feb 19 '21 at 10:00
  • 3
    `websocket::listen([this](Json::Value& json) { return this->callback(json); }, this->path);` (both `this->` might be omitted). – Jarod42 Feb 19 '21 at 10:01

1 Answers1

1

There are two problems:

One possible approach is to amend your websocket interface and accept an optional void* context parameter like this:

using cb = int(*)(Json::Value &json, void* ctx);

static void websocket::listen(cb callback, const char* address, void* ctx = nullptr)
{
    ...
}

int app::callback(Json::Value& json)
{
    return db->insert(json);
}

void app::start()
{
    websocket::listen([] (Json::Value &json, void* ctx) -> int {
        return static_cast<app*>(ctx)->callback(json);
    }, path, this);
}

If you prefer more type safety for this erasure, you can consider std::any:

using cb = int(*)(Json::Value &json, std::any ctx);

static void websocket::listen(cb callback, const char* address, std::any ctx = {})
{
    ...
}

int app::callback(Json::Value& json)
{
    return db->insert(json);
}

void app::start()
{
    websocket::listen([] (Json::Value &json, const std::any& ctx) -> int {
        return std::any_cast<app*>(ctx)->callback(json);
    }, path, this);
}

The std::any_cast will throw a std::bad_any_cast if ctx was empty or storing a pointer that wasn't a type erased app*.

Patrick Roberts
  • 49,224
  • 10
  • 102
  • 153