Bryan Koch
Bryan Koch

Reputation: 13

Pointer-to-Function and Pointer-to-Object Semantics

I'm having issues with getting a partially-qualified function object to call later, with variable arguments, in another thread.

In GCC, I've been using a macro and typedef I made but I'm finishing up my project an trying to clear up warnings.

#define Function_Cast(func_ref) (SubscriptionFunction*) func_ref
typedef void(SubscriptionFunction(void*, std::shared_ptr<void>));

Using the Function_Cast macro like below results in "warning: casting between pointer-to-function and pointer-to-object is conditionally-supported"

Subscriber* init_subscriber = new Subscriber(this, Function_Cast(&BaseLoaderStaticInit::init), false);

All I really need is a pointer that I can make a std::bind<function_type> object of. How is this usually done?

Also, this conditionally-supported thing is really annoying. I know that on x86 my code will work fine and I'm aware of the limitations of relying on that sizeof(void*) == sizeof(this*) for all this*.

Also, is there a way to make clang treat function pointers like data pointers so that my code will compile? I'm interested to see how bad it fails (if it does).

Relevant Code:

#define Function_Cast(func_ref) (SubscriptionFunction*) func_ref
typedef void(SubscriptionFunction(void*, std::shared_ptr<void>));
typedef void(CallTypeFunction(std::shared_ptr<void>));

Subscriber(void* owner, SubscriptionFunction* func, bool serialized = true) {
    this->_owner = owner;
    this->_serialized = serialized;
    this->method = func;

    call = std::bind(&Subscriber::_std_call, this, std::placeholders::_1);
}

void _std_call(std::shared_ptr<void> arg) { method(_owner, arg); }

Upvotes: 0

Views: 128

Answers (2)

kfsone
kfsone

Reputation: 24269

The problem here is that you are trying to use a member-function pointer in place of a function pointer, because you know that, under-the-hood, it is often implemented as function(this, ...).

struct S {
    void f() {}
};

using fn_ptr = void(*)(S*);

void call(S* s, fn_ptr fn)
{
    fn(s);
    delete s;
}

int main() {
    call(new S, (fn_ptr)&S::f);
}

http://ideone.com/fork/LJiohQ

But there's no guarantee this will actually work and obvious cases (virtual functions) where it probably won't.

Member functions are intended to be passed like this:

void call(S* s, void (S::*fn)())

and invoked like this:

(s->*fn)();

http://ideone.com/bJU5lx

How people work around this when they want to support different types is to use a trampoline, which is a non-member function. You can do this with either a static [member] function or a lambda:

auto sub = new Subscriber(this, [](auto* s){ s->init(); });

or if you'd like type safety at your call site, a templated constructor:

template<typename T>
Subscriber(T* t, void(T::*fn)(), bool x);

http://ideone.com/lECOp6

If your Subscriber constructor takes a std::function<void(void))> rather than a function pointer you can pass a capturing lambda and eliminate the need to take a void*:

new Subscriber([this](){ init(); }, false);

Upvotes: 0

Richard Hodges
Richard Hodges

Reputation: 69942

it's normally done something like this:

#include <functional>
#include <memory>

struct subscription
{
  // RAII unsubscribe stuff in destructor here....
};

struct subscribable
{
  subscription subscribe(std::function<void()> closure, std::weak_ptr<void> sentinel)
  {
    // perform the subscription

    return subscription {
      // some id so you can unsubscribe;
    };
  }


  //
  //

  void notify_subscriber(std::function<void()> const& closure, 
                         std::weak_ptr<void> const & sentinel)
  {
    if (auto locked = sentinel.lock())
    {
      closure();
    }
  }
};

Upvotes: 0

Related Questions