1

I've looking for how to cast class member to C-style callback.

Recentrly i found answer with special bind hack allows to bind class members to C-style callbacks:

https://stackoverflow.com/a/39524069/5405443

I have this working code to bind function MyClass::f to C function f: But i want to avoid explicit passing cb_type as template parameter to c_bind function. In provided example CB has type void (*)(int) and Func template parameter has void (MyClass::*)(int) type.

template<typename CB, typename Func, typename... Params>
CB* c_bind(std::_Bind<Func(Params...)> function) {
    return Callback<typename ActualType<CB>::type, __COUNTER__, Func>::getCallback(function);
}

typedef void (cb_type)(int);

class MyClass {
public:
    void f(int x) {
        std::cout << "Hello from MyClass::f(int), value: " << x << std::endl;
    }
};

int main() {
    MyClass mc;

    auto f = c_bind<cb_type>(std::bind(&MyClass::f, mc, std::placeholders::_1));
    //                ^ how to avoid explicit callback type declaration here?
    f(10);

    return 0;
}

Also i found this piece of code (https://gist.github.com/vikchopde/73b62314379f733e8938f11b246df49c) for "unwrapping" some kind of functions.

bool ok = fu::is_unwrappable<decltype(&MyClass::f)>::value; // always false
// fu::unwrap_function<decltype(&MyClass::f)>::type::function_ptr blah; // won't compile

but it won't work by unknown to me reason.

My question is there any workaround to extract return type and args list from type with class-memeber pointer like void (MyClass::*)(int) and contruct C-like type void (*)(int) ?

Thank you for any help!

vilu
  • 63
  • 4
  • 2
    You are not going to figure this out via random Google searches, and by reading random code on Github. You need to thoroughly and fundamentally understand ***why*** you cannot cast it like this, and what `std::bind` does, and how it works in this case. You need to understanding ***the fundamental principles*** involved, which should be [fully explained in every good C++ book](https://stackoverflow.com/questions/388242/the-definitive-c-book-guide-and-list). You may not want to hear someone telling you "go read a book and learn this", but this what you must do, in order to do this correctly. – Sam Varshavchik Apr 10 '20 at 22:42
  • 1
    I used to know how to do this, I'll have to dig up the code.  It involves making a bit of machine code in a struct adding the pointer to the object. Calling PrestoChangoSelector. Then typecasting the struct to a C function. The idea is the C function gets called, the object is pushed on the stack with the parameters, then the C++ function is called (via a jump instruction). – QuentinUK Apr 10 '20 at 22:51
  • Sorry, with the DarkMode I thought this was Reddit! – QuentinUK Apr 10 '20 at 22:55
  • 1
    `std::_Bind` is an implementation detail of the standard library implementation. You are not supposed to use it in your code. Instead use a lambda and `std::function` as the linked code does. – walnut Apr 10 '20 at 23:27

2 Answers2

1

Well, in C++17, you are allowed to pass an arbitrary non-type parameter to a class with template<auto>. Therefore, we could store MyClass::f as a template parameter and parse its type with decltype. After passing this type to another templated class, we are able to extract desired types using template specialization.

The code below shows how to construct a C-style function wrapper<>::func_type.

Since you seem to bind an object to its member function, I additionally write the demo code to do this by invoking wrapper<>::bind. Hope it helps.

class MyClass {
public:
    void f(int x) {
    std::cout << "Hello from MyClass::f(int), value: " << x << std::endl;
    }
};

void f(int x) {
    std::cout << "Hello from f(int), value: " << x << std::endl;
}

template<auto F>
struct wrapper
{
    template<typename> struct inner;

    template<class Cls, typename Ret, typename... Args>
    struct inner<Ret(Cls::*)(Args...)>
    {
        using func_type = Ret(Args...);

        static auto bind(Cls *obj)
        {
            return [=](Args ...args){
                return (obj->*F)(std::forward<Args>(args)...);
            };
        }
    };

    using func_type = typename inner<decltype(F)>::func_type;
    static const constexpr auto bind = inner<decltype(F)>::bind;
};

int main() {
    MyClass mc;

    auto h = wrapper<&MyClass::f>::bind(&mc);
    h(10);

    using func_t = typename wrapper<&MyClass::f>::func_type;
    std::function<func_t> g = f;
    g(1);
    return 0;
}
Dappur
  • 19
  • 4
0

First of all i would like to thank @Dappur for nice example. Using your guide i will rewrite my ugly bind interface with std::_Bind usage later. Also i want to thank @Sam Varshavchik for mentioning that set of C++ books. I'll start reading it to become C++ grandmaster like you to learn how "why i cannot cast it like this". But unfortunately with my poor c++ experience I can still do it now. Here is working code:

template<class T, unsigned int n, class CallerType>
struct CallbackWrapper;

template<class Ret, class... Params, unsigned int n, class CallerType>
struct CallbackWrapper<Ret(Params...), n, CallerType> {
    static auto get(std::function<Ret(Params...)>&& fn) -> Ret(*)(Params...) {
        func = fn;
        return static_cast<Ret(*)(Params...)>(CallbackWrapper<Ret(Params...), n, CallerType>::callback);
    }

private:
    static std::function<Ret(Params...)> func;

    static Ret callback(Params... args) {
        return func(args...);
    }
};

template<class Ret, class... Params, unsigned int n, class CallerType>
std::function<Ret(Params...)> CallbackWrapper<Ret(Params...), n, CallerType>::func;

template<typename T>
struct lambda_to_stdf {
    using type = void;
};

template<typename Ret, typename Class, typename... Args>
struct lambda_to_stdf<Ret(Class::*)(Args...) const> {
    using type = std::function<Ret(Args...)>;
};

template<class Ret, class Cls, class... Args1, class... Args2>
auto c_bind(std::_Bind<Ret(Cls::*(Cls, Args1...))(Args2...)> function) -> Ret(*)(Args2...) {
    return CallbackWrapper<Ret(Args2...), __COUNTER__, Ret(Cls::*(Cls, Args1...))(Args2...)>::get(std::move(function));
}

template<class Ret, class... Args>
auto c_bind(std::function<Ret(Args...)> function) -> Ret(*)(Args...) {
    return CallbackWrapper<Ret(Args...), __COUNTER__, std::function<Ret(Args...)>>::get(std::move(function));
}

template<class F>
auto c_bind(F function) -> decltype(c_bind((typename lambda_to_stdf<decltype(&F::operator())>::type)(function))) {
    return c_bind((typename lambda_to_stdf<decltype(&F::operator())>::type)(function));
}

Usage:

class MyClass {
public:
    void f(int x) {
        std::cout << "Hello from MyClass::f(int), value: " << x << std::endl;
    }
};

int main() {
    MyClass mc;

    auto f = c_bind(std::bind(&MyClass::f, mc, std::placeholders::_1));
    f(10);

    std::function<void(int)> stdf = [](int v) {
        std::cout << "hello from std::function, value: " << v << std::endl;
    };
    auto f2 = c_bind(stdf);
    f2(100);

    auto f3 = c_bind([](int v) -> int {
        std::cout << "hello from lambda, value: " << v << std::endl;
        return 5.0f;
    });
    f3(1000);

    return 0;
}

Hope it will be helpful for someone.

vilu
  • 63
  • 4