Ich habe mit C++-Lambdas und ihrer impliziten Konvertierung in Funktionszeiger gespielt. Mein Ausgangsbeispiel war die Verwendung als Callback für die ftw-Funktion. Dies funktioniert wie erwartet.
#include <ftw.h>
#include <iostream>
using namespace std;
int main()
{
auto callback = [](const char *fpath, const struct stat *sb,
int typeflag) -> int {
cout << fpath << endl;
return 0;
};
int ret = ftw("/etc", callback, 1);
return ret;
}
Nachdem Sie es geändert haben, um Captures zu verwenden:
int main()
{
vector<string> entries;
auto callback = [&](const char *fpath, const struct stat *sb,
int typeflag) -> int {
entries.push_back(fpath);
return 0;
};
int ret = ftw("/etc", callback, 1);
for (auto entry : entries ) {
cout << entry << endl;
}
return ret;
}
Ich habe den Compiler-Fehler:
error: cannot convert ‘main()::<lambda(const char*, const stat*, int)>’ to ‘__ftw_func_t {aka int (*)(const char*, const stat*, int)}’ for argument ‘2’ to ‘int ftw(const char*, __ftw_func_t, int)’
Nach einigem Lesen. Ich habe diese Lambdas mithilfe von Captures gelernt kann nicht implizit konvertiert werden zu Funktionszeigern.
Gibt es dafür eine Problemumgehung? Bedeutet die Tatsache, dass sie nicht “implizit” konvertiert werden können, dass sie “explizit” konvertiert werden können? (Ich habe Casting versucht, ohne Erfolg). Was wäre eine saubere Möglichkeit, das Arbeitsbeispiel so zu ändern, dass ich die Einträge mit Lambdas an ein Objekt anhängen könnte?

Jay West
Ich bin gerade auf dieses Problem gestoßen.
Der Code lässt sich ohne Lambda-Erfassungen gut kompilieren, aber bei der Lambda-Erfassung tritt ein Typkonvertierungsfehler auf.
Lösung mit C++11 ist zu verwenden std::function
(Bearbeiten: Eine andere Lösung, die keine Änderung der Funktionssignatur erfordert, wird nach diesem Beispiel gezeigt). Sie können auch verwenden boost::function
(der tatsächlich deutlich schneller läuft). Beispielcode – so geändert, dass er kompiliert, kompiliert mit gcc 4.7.1
:
#include <iostream>
#include <vector>
#include <functional>
using namespace std;
int ftw(const char *fpath, std::function<int (const char *path)> callback) {
return callback(fpath);
}
int main()
{
vector<string> entries;
std::function<int (const char *fpath)> callback = [&](const char *fpath) -> int {
entries.push_back(fpath);
return 0;
};
int ret = ftw("/etc", callback);
for (auto entry : entries ) {
cout << entry << endl;
}
return ret;
}
Bearbeiten: Ich musste dies erneut überprüfen, als ich auf Legacy-Code stieß, in dem ich die ursprüngliche Funktionssignatur nicht ändern konnte, aber dennoch Lambdas verwenden musste. Nachfolgend finden Sie eine Lösung, bei der die Funktionssignatur der ursprünglichen Funktion nicht geändert werden muss:
#include <iostream>
#include <vector>
#include <functional>
using namespace std;
// Original ftw function taking raw function pointer that cannot be modified
int ftw(const char *fpath, int(*callback)(const char *path)) {
return callback(fpath);
}
static std::function<int(const char*path)> ftw_callback_function;
static int ftw_callback_helper(const char *path) {
return ftw_callback_function(path);
}
// ftw overload accepting lambda function
static int ftw(const char *fpath, std::function<int(const char *path)> callback) {
ftw_callback_function = callback;
return ftw(fpath, ftw_callback_helper);
}
int main() {
vector<string> entries;
std::function<int (const char *fpath)> callback = [&](const char *fpath) -> int {
entries.push_back(fpath);
return 0;
};
int ret = ftw("/etc", callback);
for (auto entry : entries ) {
cout << entry << endl;
}
return ret;
}
Da das Erfassen von Lambdas einen Zustand beibehalten muss, gibt es keine einfache “Problemumgehung”, da dies der Fall ist nicht nur gewöhnliche Funktionen. Der springende Punkt bei einem Funktionszeiger ist, dass er auf eine einzelne, globale Funktion zeigt und diese Information keinen Platz für einen Zustand hat.
Die nächste Problemumgehung (die im Wesentlichen die Statefulness verwirft) besteht darin, eine Art globale Variable bereitzustellen, auf die von Ihrem Lambda/Ihrer Lambda-Funktion zugegriffen wird. Beispielsweise könnten Sie ein herkömmliches Funktorobjekt erstellen und ihm eine statische Elementfunktion zuweisen, die auf eine eindeutige (globale/statische) Instanz verweist.
Aber das macht den ganzen Zweck der Lambdas-Erfassung zunichte.

Jewgeni Karpow
ORIGINAL
Lambda-Funktionen sind sehr praktisch und reduzieren einen Code. In meinem Fall brauchte ich Lambdas für die parallele Programmierung. Aber es erfordert Erfassungs- und Funktionszeiger. Meine Lösung ist hier. Aber seien Sie vorsichtig mit dem Umfang der Variablen, die Sie erfasst haben.
template<typename Tret, typename T>
Tret lambda_ptr_exec(T* v) {
return (Tret) (*v)();
}
template<typename Tret = void, typename Tfp = Tret(*)(void*), typename T>
Tfp lambda_ptr(T& v) {
return (Tfp) lambda_ptr_exec<Tret, T>;
}
Beispiel
int a = 100;
auto b = [&]() { a += 1;};
void (*fp)(void*) = lambda_ptr(b);
fp(&b);
Beispiel mit Rückgabewert
int a = 100;
auto b = [&]() {return a;};
int (*fp)(void*) = lambda_ptr<int>(b);
fp(&b);
AKTUALISIEREN
Verbesserte Version
Es ist eine Weile her, seit der erste Beitrag über C++-Lambda mit Captures als Funktionszeiger veröffentlicht wurde. Da es für mich und andere Leute brauchbar war, habe ich einige Verbesserungen vorgenommen.
Die C-Zeiger-API der Standardfunktion verwendet die Konvention void fn(void* data). Standardmäßig wird diese Konvention verwendet und Lambda sollte mit einem void*-Argument deklariert werden.
Verbesserte Implementierung
struct Lambda {
template<typename Tret, typename T>
static Tret lambda_ptr_exec(void* data) {
return (Tret) (*(T*)fn<T>())(data);
}
template<typename Tret = void, typename Tfp = Tret(*)(void*), typename T>
static Tfp ptr(T& t) {
fn<T>(&t);
return (Tfp) lambda_ptr_exec<Tret, T>;
}
template<typename T>
static void* fn(void* new_fn = nullptr) {
static void* fn;
if (new_fn != nullptr)
fn = new_fn;
return fn;
}
};
Beispiel
int a = 100;
auto b = [&](void*) {return ++a;};
Konvertieren von Lambda mit Captures in einen C-Zeiger
void (*f1)(void*) = Lambda::ptr(b);
f1(nullptr);
printf("%d\n", a); // 101
Kann auch so verwendet werden
auto f2 = Lambda::ptr(b);
f2(nullptr);
printf("%d\n", a); // 102
Falls Rückgabewert verwendet werden soll
int (*f3)(void*) = Lambda::ptr<int>(b);
printf("%d\n", f3(nullptr)); // 103
Und falls Daten verwendet werden
auto b2 = [&](void* data) {return *(int*)(data) + a;};
int (*f4)(void*) = Lambda::ptr<int>(b2);
int data = 5;
printf("%d\n", f4(&data)); // 108

Wladimir Talybin
Unter Verwendung der lokal globalen (statischen) Methode kann dies wie folgt durchgeführt werden
template <class F>
auto cify_no_args(F&& f) {
static F fn = std::forward<F>(f);
return [] {
return fn();
};
}
Angenommen, wir haben
void some_c_func(void (*callback)());
So wird die Nutzung sein
some_c_func(cify_no_args([&] {
// code
}));
Dies funktioniert, weil jedes Lambda eine eindeutige Signatur hat, sodass es kein Problem ist, es statisch zu machen. Es folgt ein generischer Wrapper mit einer variablen Anzahl von Argumenten und einem beliebigen Rückgabetyp, der dieselbe Methode verwendet.
template <class F>
struct lambda_traits : lambda_traits<decltype(&F::operator())>
{ };
template <typename F, typename R, typename... Args>
struct lambda_traits<R(F::*)(Args...)> : lambda_traits<R(F::*)(Args...) const>
{ };
template <class F, class R, class... Args>
struct lambda_traits<R(F::*)(Args...) const> {
using pointer = typename std::add_pointer<R(Args...)>::type;
static pointer cify(F&& f) {
static F fn = std::forward<F>(f);
return [](Args... args) {
return fn(std::forward<Args>(args)...);
};
}
};
template <class F>
inline typename lambda_traits<F>::pointer cify(F&& f) {
return lambda_traits<F>::cify(std::forward<F>(f));
}
Und ähnliche Verwendung
void some_c_func(int (*callback)(some_struct*, float));
some_c_func(cify([&](some_struct* s, float f) {
// making use of "s" and "f"
return 0;
}));
Hehe – eine ziemlich alte Frage, aber trotzdem…
#include <iostream>
#include <vector>
#include <functional>
using namespace std;
// We dont try to outsmart the compiler...
template<typename T>
int ftw(const char *fpath, T callback) {
return callback(fpath);
}
int main()
{
vector<string> entries;
// ... now the @ftw can accept lambda
int ret = ftw("/etc", [&](const char *fpath) -> int {
entries.push_back(fpath);
return 0;
});
// ... and function object too
struct _ {
static int lambda(vector<string>& entries, const char* fpath) {
entries.push_back(fpath);
return 0;
}
};
ret = ftw("/tmp", bind(_::lambda, ref(entries), placeholders::_1));
for (auto entry : entries ) {
cout << entry << endl;
}
return ret;
}

Zhang
Meine Lösung, verwenden Sie einfach einen Funktionszeiger, um auf ein statisches Lambda zu verweisen.
typedef int (* MYPROC)(int);
void fun(MYPROC m)
{
cout << m(100) << endl;
}
template<class T>
void fun2(T f)
{
cout << f(100) << endl;
}
void useLambdaAsFunPtr()
{
int p = 7;
auto f = [p](int a)->int {return a * p; };
//fun(f);//error
fun2(f);
}
void useLambdaAsFunPtr2()
{
int p = 7;
static auto f = [p](int a)->int {return a * p; };
MYPROC ff = [](int i)->int { return f(i); };
//here, it works!
fun(ff);
}
void test()
{
useLambdaAsFunPtr2();
}

Gemeinschaft
Es gibt eine hackige Methode, um ein erfassendes Lambda in einen Funktionszeiger umzuwandeln, aber Sie müssen vorsichtig sein, wenn Sie es verwenden:
https://codereview.stackexchange.com/questions/79612/c-ifying-a-capturing-lambda
Ihr Code würde dann so aussehen (Achtung: Brain Compiler):
int main()
{
vector<string> entries;
auto const callback = cify<int(*)(const char *, const struct stat*,
int)>([&](const char *fpath, const struct stat *sb,
int typeflag) -> int {
entries.push_back(fpath);
return 0;
});
int ret = ftw("/etc", callback, 1);
for (auto entry : entries ) {
cout << entry << endl;
}
return ret;
}
9883300cookie-checkC++-Lambda mit Captures als Funktionszeigeryes
Welchen Compiler verwendest du? ist es VS10?
– Ramon Zarazua B.
21. Oktober 2011 um 16:40 Uhr
gcc-Version 4.6.1 20110801 [gcc-4_6-branch revision 177033] (SUSE-Linux)
– duncan
21. Oktober 2011 um 16:45 Uhr
Normalerweise erfolgt die Zustandsübergabe in C an Rückrufe über ein zusätzliches Argument für den Rückruf (normalerweise vom Typ type
void *
). Wenn die von Ihnen verwendete Bibliothek dieses zusätzliche Argument zulässt, finden Sie eine Problemumgehung. Andernfalls haben Sie keine Möglichkeit, sauber das zu erreichen, was Sie tun möchten.– Alexander C.
21. Oktober 2011 um 16:51 Uhr
Jawohl. Mir ist klar, dass die API von ftw.h und nftw.h fehlerhaft ist. Ich werde es mit fts.h versuchen
– duncan
21. Oktober 2011 um 17:01 Uhr
Toll! /usr/include/fts.h:41:3: error: #error “ kann nicht mit -D_FILE_OFFSET_BITS==64 verwendet werden”
– duncan
21. Oktober 2011 um 22:00 Uhr