2015-02-10 18 views
7

Używam biblioteki C w projekcie C++ 11, a biblioteka C udostępnia funkcję, która oczekuje wskaźnika funkcji. Chcę przekazać mu lambdę C++ 11, która działa poprawnie, chyba że przechwycę zmienne. Oto krótki przykład:Przekazywanie lambd z przechwytywaniem do starszych callbacków

#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; 
} 

GNU kompilator C++ daje mi następujący komunikat o błędzie w trzecim wywołaniu funkcji legacy:

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; }); 

Jak mogę rozwiązać ten problem? Czy technicznie niemożliwe jest użycie przechwytywania lambda jako wskaźnika funkcji C?

+2

Nie, tylko mniej-capture [lambda] (http://en.cppreference.com/w/cpp/language/lambda) może może być użyty jako wskaźnik funkcji. –

+0

Ogólnie każde API C, które przyjmuje wskaźnik funkcji, powinno również przyjmować pewien rodzaj "kontekstu użytkownika", który będzie przekazywany do funkcji. Czy Twój interfejs API ma jedno z tych? – newacct

Odpowiedz

9

Nie, nie można przekonwertować lamdby na wskaźnik funkcji, jeśli przechwytuje cokolwiek.

C++ Standard części § 5.1.2/6 [expr.prim.lambda] kopalni nacisk:

typ zamknięcia dla nierodzajowego lambda ekspresji bez lambda ma wychwytywania publiczną, nie-wirtualną, nie jawną funkcję konwersji stałej na wskaźnik do funkcji z łączem języka C++ (7.5) mającym ten sam parametr i typy zwracane, co operator wywołania funkcji typu zamknięcia. Wartością zwracaną przez funkcję konwersji jest adres funkcji, która po wywołaniu ma taki sam skutek, jak wywołanie operatora wywołania funkcji typu zamknięcia

5

Wskaźnik funkcji to po prostu wskaźnik do niektórych bitów kod. Brak danych.

Lambda z przechwytami również potrzebuje przechwyconych danych w celu uzyskania przydatnych wyników.

Nie ma sposobu, który mógłby działać automatycznie. Ale w zależności od tego, jak wywoływana jest funkcja wywołania zwrotnego, możliwe jest obejście tego. Twoja funkcja legacy nie zapisuje wywołania zwrotnego w dowolnym miejscu, tylko wywołuje je podczas własnego wykonania. Ponadto nie używasz wątków ani niczego w tym stylu. Dopóki te założenia trzymać, można obejść 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; 
} 
+2

Oczywiście może to spowodować problemy, jeśli chcesz wielokrotnie używać API z różnych wątków. Możesz utworzyć wątek 'legacyWrapperCallback' wątek-lokalny - ale to może spowodować kłopoty, jeśli wywołanie zwrotne dzieje się w innym wątku niż pierwotne wywołanie. –

+0

@SebastianRedl Tak, już to odnotowałem. To również nie zadziała, jeśli wywołanie zwrotne stanie się po tym, jak "dziedzictwo" już powróci (pomyśl o subskrybowaniu zdarzeń). – hvd

Powiązane problemy