5

I would like to pass my class method as an argument to a (third-party) function (listner - that cannot be changed) that accepts a function pointer and a void*. Following is an example:

#include <functional>

typedef void(*pfnc) (void*);

struct Foo
{
    static void static_foo(void*)
    {
    }

    void foo(void*)
    {
    }

    void listner(pfnc f, void* p)
    {
       f(p);
    }

    void test()
    {
        listner(static_foo); // works with static method

        auto f = [](void*) {};
        listner(f); // works with lambda

        std::function<void(void*)> stdf = std::bind(&Foo::foo, this, std::placeholders::_1);
        listner(stdf);  // does not compile with not static method
    }
};

Unfortunately my solution does not compile. What do I have to change?

Nick
  • 10,309
  • 21
  • 97
  • 201
  • 1
    Function pointers don't have state. You're going to have to make use of the `void*` parameter. On the plus side, the parameter exists, unlike some other specific C APIs I've come across. – chris Oct 11 '17 at 16:14
  • 1
    Are you sure that the `listener` method accepts only `pfnc`? Usually such methods accept a user specified `void *` parameter that is passed during the callback to the handler method... – Daniel Trugman Oct 11 '17 at 22:11
  • I usually use a lambda that captures this to call the method. – Colin Pitrat Oct 12 '17 at 06:52
  • @DanielTrugman you are right, going to update the function. – Nick Oct 13 '17 at 08:19

3 Answers3

1

From the look of the callback signal, the listener API takes a pointer to void as 'user defined data'. You can pass this as the data and a small stateless proxy function to route to the handler on Foo:

typedef void(*pfnc) (void*);

struct Foo
{
    static void static_foo(void*)
    {
    }

    void foo()
    {
    }

    void listner(pfnc f, void* user_data)
    {

        // eventually calls
        f(user_data);
    }

    void test()
    {
        listner(static_foo, nullptr); // works with static method

        auto f = [](void*) {
        };
        listner(f, nullptr); // works with lambda

        listner([](void* pv)
        {
            reinterpret_cast<Foo*>(pv)->foo();
        }, this);
    }

};
Richard Hodges
  • 68,278
  • 7
  • 90
  • 142
1

For the example in the question it would be possible to pass the member function pointer. But of course the instance on which that member is to be called must be known. If the calling function is also a member function then we can use this to call the member function passed via the pointer.

struct Foo
{
    static void static_foo(void*)
    {
    }

    void foo(void*)
    {
    }

    void listner(pfnc f)
    {
    }

    void listner(void(Foo::*f)(void*))
    {
        (this->*f)(nullptr); // call member function pointer for this
    }

    void test()
    {
        listner(static_foo); // works with static method

        auto f = [](void*) {};
        listner(f); // works with lambda

        listner(&Foo::foo); // pass pointer to member function
    }
};
wally
  • 10,717
  • 5
  • 39
  • 72
0

Your example 'works' with a static member function because these are almost equivalent to plain old functions

It also 'works' with the lambda you provide because it is a capture-less lambda which can be converted to a function pointer (cf Passing lambda as function pointer) It will not work with a lambda with a capture however, because, as chris said, function pointers don't have state, i.e. there is no place to store what would be captured.

The same goes for the bind. You can store it in a std::function because such an object has a state, but you cannot store it in a plain (stateless) function pointer.

If you're not convinced, have a look at what stdf.target_type() tells you and compare it with typeid(static_foo).name() You can also have a look at the return type of std::bind

In the end, as others have said, you will have to embark your object in the user data, that is, in the void*. It could be worth investigating whether you can wrap that in a helper function taking an std::function as a parameter...

David Parsons
  • 945
  • 8
  • 4