Naios / continuable

C++14 asynchronous allocation aware futures (supporting then, exception handling, coroutines and connections)
https://naios.github.io/continuable/
MIT License
815 stars 44 forks source link

how do you convert a variadic function with callback to a continuable? #14

Closed bennywhipple closed 5 years ago

bennywhipple commented 5 years ago

@Naios

I would like to convert a function with the following signature into a continuable:

typedef void (CallbackFn)(struct CallbackContext , void , void ); int async_call(const char param, CallbackFn cb, void privateData, const char qformat, ...);

The closest example I've found is the promisify::from example in the tutorial but promisify wants the callback to have a different signature. It would also be nice to retain the varargs in the top-level call but perhaps it is difficult. I like the design of your interface and it would fit in nicely to my project but the template error messages can be daunting.

Any help appreciated - hopefully it is of interest to other users of your library, thanks!

Naios commented 5 years ago

It seems that your callback is using the technique used by libuv such that you have to specify a callback with a fixed signature that allows you to pass a custom argument through a void* pointer, which is then castable to your private data (the continuable promise in this case). For the continuable library this isn't an ideal case because you will have to move the promise to the heap as following (without being capable of elliding the allocation through small buffer optimization):


typedef void(CallbackFn)(void* context, void*, void*);
int async_call(const char* param, CallbackFn cb, void* privateData,
               const char* qformat, ...) {
  //
  return 0;
}

auto my_async_call(const char* param, const char* qformat, ...) {
  // handle the varargs in front
  // assuming that param stays alive
  return cti::make_continuable<void>([param](auto&& promise) {
    using type_t = std::decay_t<decltype(promise)>;
    type_t* heap_promise = new type_t(std::forward<decltype(promise)>(promise));

    async_call(
        param,
        [](void* context, void*, void*) {
          // A lambda without any capture is convertible to a function pointer
          std::unique_ptr<type_t> ptr(static_cast<type_t*>(context));
          ptr->set_value(/*set the result here*/);
        },
        heap_promise, ""); // Order probably not correct
  });
}

Probably it is easier here to create the callback for this directly rather than wrapping the calling function through promisify<>::from.

This C-style technique is not ideal from using it out of C++, because you loose a lot of benefits C++ offers, for instance if the callback is never executed you will leak the callback memory here. Also type safety is ignored completely here (this is based on the callback style rather than the library and libuv for instance is affected by those issues).

For the varargs you have to store them in front before creating the continuable because they are usually passed on the stack and don't work well with the lazy evaluation technique used by continuable.

bennywhipple commented 5 years ago

Perhaps I'm wrong, but my impression is that your framework is aimed at efficient type/memory management and Asio's interface is more suited to this than the one I'm using.. so I will take a look at it. Thanks for your help.