Search code examples
c++embeddedfunction-pointersmicroprocessors

C++ wrapper for bound member function on resource constrained MCU


I am trying to implement a pointer to member function wrapper where the return type and parameter types are known but the class type of the member function is not known. This is for a C++ project targeting resource constrained (No heap allocation/c++ standard library) microprocessor.

The following code seems to work (you can run it HERE) and it fulfills our general requirements, but it uses reinterpret_cast hackery.

//can't use any c++ std libs or heap allocation
#include <cstdio>

template<typename Ret,typename... Args>
struct member_cb
{

    template<typename T>
    void set_cb(T *obj_,Ret (T::*func)(Args...))
    {
        obj = reinterpret_cast<member_cb*>(obj_);
        cb = reinterpret_cast<Ret (member_cb::*)(Args...)>(func);
    }

    Ret operator()(Args... num)
    {
        return (obj->*cb)(num...);
    }

    member_cb* obj;
    Ret (member_cb::*cb)(Args...);

};

struct Bar{

    void someFunc(int n2){
        printf("someFunc called with parameter: %d\n",n2);
    }

};

int main()
{ 
    //class type of Bar is not known at wrapper creation
    member_cb<void,int> foo;
    
    Bar bar;
    
    foo.set_cb(&bar, &Bar::someFunc);
    
    foo(42);
}

I want to do this without the reinterpret_cast. I believe there is a solution using a lambda that is created by the set_cb() function something like this:

template<typename Ret,typename... Args>
struct member_cb
{
    template<typename T>
    void set_cb(T *obj_,Ret (T::*func)(Args...))
    {
        cb_func = [=](Args...num)
        {
            (obj_->*func)(num...);
        };
    }

    Ret operator()(Args... args)
    {
        return cb_func(args...);
    }

    Ret (*cb_func)(Args...);
}

The above code will not compile (you can try it HERE). Problem is that the regular function pointers like cb_func can only point to lambdas that have no captures, and the lambda has to capture the obj_ and the func parameters. Any suggestions on making this work would be appreciated.


Solution

  • A simple, compiler-independent solution:

    template <typename Signature>
    struct member_cb;
    
    template <typename Ret, typename... Args>
    struct member_cb<Ret(Args...)>
    {
        template <typename T, Ret (T::*func)(Args...)>
        static Ret wrapper(void *object, Args&&... args) {
            T *o = reinterpret_cast<T *>(object);
            return (o->*func)(std::forward<Args>(args)...);
        }
        
        template <typename T, Ret (T::*func)(Args...)>
        void set_cb(T *obj_)
        {
            obj = obj_;
            cb_func = wrapper<T, func>;
        }
    
        // since C++17:
        template <auto func, typename T>
        void set_cb(T *obj_)
        {
            obj = obj_;
            cb_func = wrapper<T, func>;
        }
    
        Ret operator() (Args&& ...args)
        {
            return cb_func(obj, std::forward<Args>(args)...);
        }
    
        void *obj;
        Ret (*cb_func)(void *, Args&&...);
    };
    
    ...
    member_cb<void(int)> foo;
    foo.set_cb<Bar, &Bar::someFunc>(&bar);
    foo.set_cb<&Bar::someFunc>(&bar); // since C++17
    

    The key limitation is that someFunc must be known at compile-time at the place set_cb is called, so you can’t take arbitrary method pointer and convert it to a member_cb. It is possible to change the object keeping the method pointer intact, though (but that’s not type-safe).

    Another possible problem is that the compiler may need to emit a wrapper instance for each and every method that is ever used as a callback.

    The upside is that it should work on any C++11-compliant compiler (C++17 for nicer syntax), and member_cb itself is small.