C-style Callbacks in C++11 C-style Callbacks in C++11 curl curl

C-style Callbacks in C++11


A lambda that captures context variables cannot be converted to a bare function pointer because that would make it impossible to carry the captured state along. What you've shown in the example is the right way to go about dealing with the problem of invoking C++ member functions via a C callback.

You could replace c_callback_wrapper with a capture less lambda, if you find that any more appealing.

curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION,                  [](char *ptr, size_t size, size_t nmemb, void *userdata) {                    // invoke the member function via userdata                    auto p = static_cast<MyClass *>(userdata);                    return p->actualCallback(ptr, size, nmemb, userdata);                 });

Note that you should probably get rid of the last parameter to the actualCallback() member function, since that is just the this pointer, which the non-static member function shouldn't need to be passed explicitly.


Here is what I would do (please note that I only know enough about C++ to blow off the occasional foot):

#include <iostream>#include <functional>void external_c_function(void cb(void *), void *userdata){    cb(userdata);}void c_callback_wrapper(void *userdata){    auto &lambda = *static_cast< std::function<void(void)>* >(userdata);    std::cout << "calling lambda" << std::endl;    lambda();}int main(void){    int foo = 42;    std::function<void(void)> lambda = [&] { std::cout << foo << std::endl; };    external_c_function(c_callback_wrapper, &lambda);    return 0;}


template<class T>using type=T; // makes some declarations easiertemplate<class F>struct callback_t;template<class F, class Sig>struct cast_helper_pvoid_last;template<class F, class R, class... Args>struct cast_helper_pvoid_last<F, R(Args...)> {  type<R(*)(Args..., void*)> operator()() const {    return [](Args... args, void* pvoid)->R {      auto* callback = static_cast<callback_t<F>*>(pvoid);      return callback->f( std::forward<Args>(args)... );    };  }};template<class F>struct callback_t {  F f;  void* pvoid() { return this; }  template<class Sig>  auto pvoid_at_end()->decltype( cast_helper_pvoid_last<F, Sig>{}() ) {    return cast_helper_pvoid_last<F,Sig>{}();  }};template<class T>using decay_t=typename std::decay<T>::type;template<class F>callback_t<decay_t<F>> make_callback( F&& f ) { return {std::forward<F>(f)}; }

Example use:

int x = 3;auto callback = make_callback( [&]( int y ) { return x+y; } );int (*func)(int, void*) = callback.pvoid_at_end<int(int)>();std::cout << func( 1, callback.pvoid() ) << "\n";

should print 4. (live example)

The lifetime of your callback_t must exceed the pvoid it produces.

I could auto-deduce the signature of the function pointer instead of requiring you to pass <int(int)> (the signature without the void*), but again that makes the code much easier.

and add this if you want the void* to be first:

template<class F, class Sig>struct cast_helper_pvoid_first;template<class F, class R, class... Args>struct cast_helper_pvoid_first<class F, R(Args...)> {  type<R(*)(void*, Args...)> operator()() const {    return [](void* pvoid, Args... args)->R {      auto* callback = static_cast<callback<F>*>(pvoid);      return callback->f( std::forward<Args>(args)... );    };  }};// inside the callback_t<?> template:  template<class Sig>  auto pvoid_at_start()->decltype( cast_helper_pvoid_first<F, Sig>{}() ) {    return cast_helper_pvoid_first<F,Sig>{}();  }

doing a void* in the middle gets trickier.

If you have calling convention issues, then we need to use a helper object

Have pvoid_at_end return a cast_helper_pvoid_last instead of calling () on it.

Then, add operator overloads to cast-to-function-pointer of each calling convention you need to support. The body is identical to the operator(), as the lambda should support any of them.

Alternatively, with some C++14 support you can change the return type of operator() to auto, and leave the code otherwise intact, and rely on direct cast-from-lambda to get the calling convention right.