1

I'm using some (somewhat C-ish) library which involves a callback mechanism. The callback functions I can provide it take a void* as a parameter so you can pass arbitrary stuff to them. For the sake of this question let's assume the lambda doesn't take any parameters, but it does capture stuff.

Now, I need to have my callback function invoke a lambda - and it must get this lambda somehow via the void *, i.e. we have

void my_callback(void * arbitrary_stuff) {
    /* magic... and somehow the lambda passed  */
    /* through `arbitrary_stuff` is invoked.   */
}

// ...

template <T>
void adapted_add_callback(MagicTypeInvolvingT actual_callback) {
    /* more magic */
    libFooAddCallback(my_callback, something_based_on_actual_callback);
}

// ...

void baz();

void bar() {
    int x;
    adapted_add_callback([x]() { /* do something with x */ });
    adapted_add_callback(baz);
}

and I want to know what to replace magic, more_magic and MagicTypeInvolvingT with.

Other than the typing challenge here, what I'm worried about, obviously, is how to make sure the data the lambda encapsulates is available on the stack for eventual use, as otherwise I should probably get some kind of segmentation fault.

Notes:

  • my_callback() should be synchronous, in the sense that it'll execute the lambda on whatever thread it is on and return when it returns. It's either the fooLibrary or the lambda itself which do asynchronicity.
einpoklum
  • 118,144
  • 57
  • 340
  • 684

3 Answers3

2

You have a couple of issues here.

One is that you can't depend on passing the lambda itself as a void *, so you'll pretty much need to pass a pointer to the lambda (well, the closure created from the lambda, if you want to be precise). That means you'll need to ensure that the lambda remains valid until the callback completes.

The second is a question about how those captures happen - capture by value, or by reference? If you capture by value, everything's fine. If you capture by reference, you also need to ensure that anything you've captured remains valid until the callback completes. If you capture a global by reference, that should normally be fine--but if you capture a local by reference, then the local (even potentially) goes out of scope before the lambda is invoked, using the reference will cause undefined behavior.

einpoklum
  • 118,144
  • 57
  • 340
  • 684
Jerry Coffin
  • 476,176
  • 80
  • 629
  • 1,111
  • While this is not a concrete answer, it's indeed useful for people to note. Emphasized the key points and +1. – einpoklum Oct 25 '17 at 20:09
2

the most straightforward way might be ( assuming the C function is guaranteed to invoke the callback exactly once, and that the lambda is valid at callback point )

void my_callback(void * arbitrary_stuff) {
    (*std::unique_ptr{ static_cast<std::function<void()>*>(arbitrary_stuff) })();
}

void adapted_add_callback( std::function<void()> actual_callback ) {
    libFooAddCallback(my_callback, new auto( std::move(actual_callback) ) );
}

if you don't want the function<> overhead you'll need to implement your own type erasure ...

Massimiliano Janes
  • 5,524
  • 1
  • 10
  • 22
  • For those of us who are slightly baffled by the last line of code: [What does \`new auto\` do?](https://stackoverflow.com/questions/15935080/what-does-new-auto-do?s=2|68.8463) – einpoklum Oct 25 '17 at 23:23
  • @einpoklum, given your latest edits to the question regarding the callback guarantees, and given that you're going to wrap the lib call anyway, I think that Quentin 's second solution is honestly preferable ( no allocations, supports up to non-movable lambdas ) – Massimiliano Janes Oct 26 '17 at 07:19
  • ... but I did not guarantee the lambda outlives the call, quite the contrary. – einpoklum Oct 26 '17 at 08:37
1

I went in a way similar to Massimiliano Janes', but without the overhead of std::function. You have to ensure that the callback is called only once by the library.

using Callback = void(*)(void*);


// Probes the type of the argument and generates a suitable cast & invoke stub
// Caution: self-destructs after use!
template <class F>
Callback cbkWrap(F &) {
    return [](void *data) {
        std::unique_ptr<F> retrieved(static_cast<F*>(data));
        (*retrieved)();
    };
}

// Moves the functor into a dynamically-allocated one
template <class F>
void *cbkFunc(F &f) {
    return new F{std::move(f)};
}

int main() {
    int x = 42;

    auto lambda = [&x] { std::cout << x << '\n'; };

    libFooAddCallback(cbkWrap(lambda), cbkFunc(lambda));
}

See it live on Coliru

If you can ensure that the lambda outlives the potential calls, you can get rid of the dynamic memory allocations and simply pas a pointer to it as data:

// Probes the type of the argument and generates a suitable cast & invoke stub
template <class F>
Callback cbkWrap(F &) {
    return [](void *data) {
        auto retrieved = static_cast<F*>(data);
        (*retrieved)();
    };
}

int main() {
    int x = 42;

    auto lambda = [&x] { std::cout << x << '\n'; };

    libFooAddCallback(cbkWrap(lambda), &lambda);
}

See it live on Coliru

There is unfortunately no way to give ownership of the lamba to the library without knowing exactly how many times it will be called.

einpoklum
  • 118,144
  • 57
  • 340
  • 684
Quentin
  • 62,093
  • 7
  • 131
  • 191
  • By `libFunction()` do you mean `libFooAddCallback()`? – einpoklum Oct 25 '17 at 16:31
  • @einpoklum I do. – Quentin Oct 25 '17 at 18:05
  • You would need another wrapper to allow me to confine it all to a single statement rather than defining the lambda variable first. – einpoklum Oct 25 '17 at 22:59
  • I would think that first example has undefined behavior and potential memory leaks... Can you explain how it should works? In particular, why you can create a unique pointer while data does not seems to be allocated on heap? And also why you can call `new` without a matching `delete`? And finally explain how you are sure to have correct behavior even it is unspecified if `cbkWrap` is called before pr after `cbkFunc`... and how you guarantee exception safety etc... – Phil1970 Oct 26 '17 at 11:08
  • @Phil1970 Of course! The `unique_ptr` takes ownership of the dynamically-allocated instance that has been `new`d in `cbkFunc` and passed through the `void *`, so that's your missing `delete`. I do not have order-of-evaluation issues since the parameter of `cbkWrap` is only there to enable type deduction (in fact, it is unnamed). And about exception safety... well, if something goes wrong before the callback is called, unfortunately that'll be a leak. But since the library has no idea that we're passing in a dynamically-allocated object, there's no way around it :/ – Quentin Oct 26 '17 at 11:14