6

In a C++11 project I'm using a C-style third-party library (curl in my case) which needs C-style callbacks.

To achieve this I used the "poiner-to-member" operator:

size_t c_callback_wrapper(char *ptr, size_t size, size_t nmemb, void *userdata)
{
    MyClass *p = (MyClass*)userdata;
    return (p->*&MyClass::actualCallback)(ptr, size, nmemb, userdata);
}


void Myclass::performSomething() {
    // register callback function
    curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, c_callback_wrapper);
    // register userdata
    curl_easy_setopt(curl, CURLOPT_WRITEDATA, this);
    ...
    res = curl_easy_perform(curl);
}

This solution actually works, but it's not satisfying for me.

What I actually want to do is to write a lambda inside my registering function.

The advantage would be locality in general: I would be able to capture some local variables and there would be no need to write a complex "actualCallback" member routine.

I already read, that it seems not to be possible to use Lambdas as C-style Functions when they capture variables.

Are there any ways to achieve this using some tricks? (e.g. playing with the calling convention of lambdas, ...)

Thanks

Praetorian
  • 106,671
  • 19
  • 240
  • 328
rralf
  • 1,202
  • 15
  • 27

3 Answers3

6

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.

Praetorian
  • 106,671
  • 19
  • 240
  • 328
  • I already tried this, but however my applications ends up in a segfault - even if I try using a noncapturing lambda... Combining third-party c-style libraries with straight-forward C++11 code always kills beauty of code... I hate it... :-( – rralf Aug 26 '14 at 19:58
  • 1
    @rralf The solution above [does work](http://ideone.com/8kyyAU), so maybe you've got something else going on. If you're using MSVC, make sure you [cast the lambda](https://stackoverflow.com/a/14169489/241631) to match the calling convention used by the C library. – Praetorian Aug 26 '14 at 20:06
  • 1
    Oh thank you, using the cast it works! (Seems that gcc also requires the cast for calling convention) – rralf Aug 26 '14 at 20:13
  • this should only work as long as the lambda doesn't capture any variables! – Christoph Aug 26 '14 at 20:18
  • Yes, I know. It'd be nice if there would be some trick to get it working when using captures... – rralf Aug 26 '14 at 20:19
3

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;
}
Christoph
  • 164,997
  • 36
  • 182
  • 240
2
template<class T>using type=T; // makes some declarations easier

template<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.

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524