kayahr
kayahr

Reputation: 22010

Pass lambdas with capturing to legacy callbacks

I'm using a C library in a C++11 project and this C library provides a function which expects a function pointer. I want to pass a C++11 lambda to it which works correctly unless I capture variables. Here is a short example:

#include <cstdio>
#include <functional>

typedef int (*Callback)();

void legacy(Callback callback) {
    printf("%i\n", callback());
}

int stdCallback() {    
    return 1; 
}

int main(int argc, char* argv[]) {
    int number = 3;    

    // Standard C callback works
    legacy(stdCallback);

    // Lambda without capturing works
    legacy([]() { return 2; });    

    // Lambda with capturing doesn't work
    legacy([&]() { return number; });

    return 0;
}

The GNU C++ compiler gives me the following error message in the third call to the legacy function:

test.cpp: In function ‘int main(int, char**)’:
test.cpp:24:36: error: cannot convert ‘main(int, char**)::<lambda()>’ to ‘Callback {aka int (*)()}’ for argument ‘1’ to ‘void legacy(Callback)’
 legacy([&]() { return number; });

How can I fix this? Or is it technically not possible to use a capturing lambda as a C function pointer?

Upvotes: 7

Views: 1044

Answers (2)

quantdev
quantdev

Reputation: 23793

No, you can't convert a lamdba to a function pointer if it captures anything.

C++ Standard, section § 5.1.2 / 6 : [expr.prim.lambda], emphasis mine :

The closure type for a non-generic lambda-expression with no lambda-capture has a public non-virtual non-explicit const conversion function to pointer to function with C ++ language linkage (7.5) having the same parameter and return types as the closure type’s function call operator. The value returned by this conversion function shall be the address of a function that, when invoked, has the same effect as invoking the closure type’s function call operator

Upvotes: 9

user743382
user743382

Reputation:

A function pointer is just that: a pointer to some bits of code. No data.

A lambda with captures also needs the captured data in order to give useful results.

There's no way that could work automatically. But, depending on how the callback function gets called, you may be able to work around it. Your legacy function doesn't save the callback anywhere, it only calls it during its own execution. Additionally, you're not using threading or anything like that. As long as those assumptions hold, you can work around the problem:

#include <cstdio>
#include <functional>

typedef int (*Callback)();

void legacy(Callback callback) {
    printf("%i\n", callback());
}

// Declared as void * outside legacyWrapper, instead of T * inside legacyWrapper,
// because no more than one variable is needed, no matter how many instantiations of
// legacyWrapper there are.
static void *legacyWrapperCallback;

template <typename T>
void legacyWrapper(T callback) {
    legacyWrapperCallback = &callback;
    legacy([]() -> int { return (*static_cast<T *>(legacyWrapperCallback))(); });
}

int stdCallback() {    
    return 1; 
}

int main(int argc, char* argv[]) {
    int number = 3;    

    // Standard C callback works
    legacy(stdCallback);

    // Lambda without capturing works
    legacy([]() { return 2; });    

    // Lambda with capturing doesn't work...
    //legacy([&]() { return number; });

    // ...unless the C++ wrapper is used
    legacyWrapper([&]() { return number; });

    return 0;
}

Upvotes: 5

Related Questions