3

Suppose I have a C function

typedef void (* callback_t)(void* data);
status_you_dont_care_about register_callback(callback_t callback, void* data);

and I want to pass a lambda as the callback.

What's the idiomatic way to do this...

  1. When the lambda captures nothing?
  2. When the lambda has captures (by value, by reference etc.)?
einpoklum
  • 118,144
  • 57
  • 340
  • 684
  • For windows, when the lambda has no captures, then Visual Studio will convert it to the "callback" that the API wants. If there are captures (closure), then it is not possible. – Michael Chourdakis Mar 28 '19 at 10:53
  • @MichaelChourdakis: `void* data` is here to pass custom user data, as pointer of the class to handle that. – Jarod42 Mar 28 '19 at 10:54
  • I don't understand. Why can't you just pass the lambda straight to the function? they should be convertible to their respective fpointers. This is of course assuming that you're compiling the C files alongside the C++ files. – Hatted Rooster Mar 28 '19 at 10:56
  • @SombreroChicken: only for capture-less lambdas. – Jarod42 Mar 28 '19 at 10:56
  • @jarod42 Right I missed that – Hatted Rooster Mar 28 '19 at 10:58
  • @MichaelChourdakis: It's not just on WIndows, it's on all platforms. – einpoklum Mar 28 '19 at 11:20
  • @SombreroChicken: TBH, the first part of the question was only added for completeness rather than because I don't know the answer. The second part is the interesting one. – einpoklum Mar 28 '19 at 11:21

1 Answers1

1

Non-capturing case

As commenters point out, a lambda implicitly converts into a function pointer of the appropriate type, so we can just write:

auto my_lambda = [](void* data) { /* do stuff here */};
register_callback(my_lambda, get_pointer_to_data());

and this will compile. Don't forget to check the return value though :-)

Capturing case

Here's what I'm doing at the moment, but I suspect it's sub-optimal.

First I make sure that the lambda captures everything that it needs so that it is itself invokable with no parameters (so that it actually does not take a void* like a callback is supposed to). Then I use this code:

template <typename Invokable>
void callback_adapter(void *invokable_on_heap)
{
    auto retyped_callback = std::unique_ptr<Invokable>{
        reinterpret_cast<Invokable*>(invokable_on_heap)
     };
    (*retyped_callback)(std::forward<Ts>(parameters)...);
    // Note: invokable_on_heap will be delete'd
}

template <typename Invokable>
void register_invokable_as_callback(Invokable callback_) {
    Invokable* invokable_on_the_heap = new Invokable(std::move(callback_));
    register_callback(&callback_adapter<Invokable>, invokable_on_the_heap);
}

Note: With this approach, the callback can only be called once. It can be adapted to the case of a callback which needs to be called many times, with deallocation upon de-registration / destruction of whatever the callbacks are called for.

einpoklum
  • 118,144
  • 57
  • 340
  • 684
  • Issue is with ownership, your current callback can only be called once. – Jarod42 Mar 28 '19 at 11:10
  • @Jarod42: `data` is not intended for passing the class, necessarily. It can be used to pass anything, which is why `my_lambda` takes it as a parameter and can do whatever it wants with it. – einpoklum Mar 28 '19 at 11:31
  • With "correct" approach of deallocation on de-registration, you then need to return (RAII) "handle" which mostly requires type erasure to be usable... (And de-register the captureless lambda also require to keep its type/value, so named function seems more appropriate). – Jarod42 Mar 28 '19 at 12:27
  • What about C vs C++ calling conventions? How can we be sure that it is safe to pass C++ function pointer to C function? [This answer](https://stackoverflow.com/a/36947588) says that this is not safe in general. – Evg May 03 '20 at 07:45
  • 1
    @Evg: 1. I'm not sure; IANALL. 2. I think this is "taken care of" somehow, through shared ABI in terms of passing arguments of types recognized in C. The answer you linked to regards C++ constructs, but here - we're simply passing `void *`'s. – einpoklum May 03 '20 at 07:56