Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Storing a container of callbacks to different caller types without polymorphism or std function

Tags:

c++

I have a class (Manager) storing callbacks to callers of different types (so I cannot template).

However, I would like to avoid polymorphism and std::function.

I tried with function pointers (see below) but I didn't realise the class type is part of the pointer type.

Is there any other way of achieving this? I don't care how "nasty" the solution might be.

#include <map>

using FuncPtr = void(*)();

struct Manager
{
    void add_callback(FuncPtr ptr)
    {
        map[0] = ptr;
    }

    std::map<int, FuncPtr> map;
};

Manager m;

struct Caller_1
{
    void x()
    {
        m.add_callback(&Caller_1::callback);
    }

    void callback(){}
};

struct Caller_2
{
    void x()
    {
        m.add_callback(&Caller_2::callback);
    }

    void callback(){}
};

int main()
{
    return 0;
}

Compiler errors:

<source>: In member function 'void Caller_1::x()':
<source>:21:24: error: cannot convert 'void (Caller_1::*)()' to 'FuncPtr' {aka 'void (*)()'}
   21 |         m.add_callback(&Caller_1::callback);
      |                        ^~~~~~~~~~~~~~~~~~~
      |                        |
      |                        void (Caller_1::*)()
<source>:7:31: note: initializing argument 1 of 'void Manager::add_callback(FuncPtr)'
    7 |     void add_callback(FuncPtr ptr)
      |                       ~~~~~~~~^~~
<source>: In member function 'void Caller_2::x()':
<source>:34:24: error: cannot convert 'void (Caller_2::*)()' to 'FuncPtr' {aka 'void (*)()'}
   34 |         m.add_callback(&Caller_2::callback);
like image 391
mezamorphic Avatar asked Jan 22 '26 16:01

mezamorphic


1 Answers

Per Eljay's suggestion, you can employ C style callbacks which use void *callbacks. There is no safety checking, but you also don't incur the overhead of polymorphism or type erasure

(as a side note: In most cases the cost of both are generally overblown -- people go to great lengths to avoid them before properly profiling of they are causing problems. Usually the readability benefits outweigh the negligible overhead. I am proceeding from the assumption you have performed those measurements and want to avoid it in this case).

This demonstrates one way you could do that. Note that you are fully responsible to ensure you pass the right context to the right callback and that the lifetime of your objects outlive the Manager.


#include <map>

using FuncPtr = void(*)(void *);
struct CallbackInfo {
    FuncPtr callbackFunc;
    void *context;
};

struct Manager
{
    void add_callback(FuncPtr ptr, void *context)
    {
        map[0] = { ptr, context };
    }

    std::map<int, CallbackInfo> map;
};

Manager m;

struct Caller_1
{
    void x()
    {
        m.add_callback(&Caller_1::callback, this);
    }

    static void callback(void *context){static_cast<Caller_1*>(context)->callback(); }
    void callback() {}
};

struct Caller_2
{
    void x()
    {
        m.add_callback(&Caller_2::callback, this);
    }

    static void callback(void *context){static_cast<Caller_2*>(context)->callback(); }
    void callback() {}
};

int main()
{
    return 0;
}
like image 129
Jeremy Richards Avatar answered Jan 25 '26 07:01

Jeremy Richards



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!