C++-Lambda mit Captures als Funktionszeiger

Lesezeit: 12 Minuten

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?

  • 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

C Lambda mit Captures als Funktionszeiger
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;
}

  • Nein, das sollte nicht die akzeptierte Antwort sein. Der Punkt ändert sich nicht ftw nehmen std::function statt Funktionszeiger…

    – Gregory Pakosz

    28. Dezember 2013 um 19:18 Uhr

  • Die zweite in dieser Antwort vorgeschlagene Lösung geht auf die Bedenken von @gregory-pakosz ein, indem sie die Originalsignatur beibehält, aber sie ist immer noch nicht großartig, weil sie einen globalen Zustand einführt. Wenn ftw ein void* userdata Argument hätte, dann würde ich die Antwort von @evgeny-karpov bevorzugen.

    – Stolz

    30. Juni 2018 um 21:54 Uhr


  • @prideout stimmte zu – ich mag den globalen Staat auch nicht. Unter der Annahme, dass die Signatur von ftw nicht geändert werden kann und dass sie keine void*-Benutzerdaten enthält, muss der Status leider irgendwo gespeichert werden. Ich bin auf dieses Problem gestoßen, als ich eine Bibliothek eines Drittanbieters verwendet habe. Dies funktioniert gut, solange die Bibliothek den Rückruf nicht erfasst und später verwendet. In diesem Fall fungiert die globale Variable einfach wie ein zusätzlicher Parameter im Aufrufstapel. Wenn die Signatur von ftw geändert werden kann, würde ich es vorziehen, std::function anstelle von void* userdata zu verwenden.

    – Jay West

    7. Juli 2018 um 1:07 Uhr


  • Dies ist eine äußerst komplizierte und nützliche Lösung, @Gregory, ich sollte Ihnen sagen, “es funktioniert”.

    – Florenz

    23. Oktober 2018 um 15:55 Uhr

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.

  • Eine sauberere Lösung besteht darin, das Lambda in einen Adapter zu packen, vorausgesetzt, der Funktionszeiger hat einen Kontextparameter.

    – Raymond Chen

    21. Oktober 2011 um 16:06 Uhr

  • @RaymondChen: Nun, wenn es Ihnen freisteht, zu definieren, wie die Funktion verwendet werden soll, dann ja, das ist eine Option. In diesem Fall wäre es jedoch noch einfacher, den Parameter einfach zu einem Argument des Lambda selbst zu machen!

    – Kerrek SB

    21. Oktober 2011 um 16:09 Uhr

  • @KerrekSB lege die globalen Variablen in a namespace und markiere sie als thread_localdas ist die ftw Ansatz, den ich gewählt habe, um etwas Ähnliches zu lösen.

    – Kjell Hedström

    21. April 2014 um 15:08 Uhr


  • “Ein Funktionszeiger zeigt auf eine einzelne, globale Funktion, und diese Information hat keinen Platz für einen Zustand.” -> Wie zum Teufel können Sprachen wie Java das dann bewerkstelligen? Nun, natürlich, weil diese einzelne, globale Funktion erstellt wird zur Laufzeit und bettet ein Der Staat (bzw Hinweis dazu) in einem eigenen Code. Dass ist der springende Punkt – es sollte nicht sei ein einzelne, globale Funktion aber mehrere globale Funktionen – eine für jedes Mal, wenn Lambda zur Laufzeit verwendet wird. Gibt es wirklich NICHTS in C++, das das tut? (Ich dachte, std::function ist genau für diesen einen Zweck gemacht)

    – Dexter

    30. Mai 2017 um 8:51 Uhr

  • @Dexter: errr .. die kurze Antwort ist nein, die lange Antwort beinhaltet eine Operatorüberladung. Egal, mein Punkt steht. Java ist eine andere Sprache, die nicht mit C++ identisch ist; Java hat keine Zeiger (oder überladbare Aufrufoperatoren) und der Vergleich funktioniert nicht gut.

    – Kerrek SB

    30. Mai 2017 um 20:17 Uhr

1646926214 89 C Lambda mit Captures als Funktionszeiger
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

  • Dies ist definitiv die bequemste Lösung, die ich gesehen habe, um ein Lambda in einen Funktionszeiger im C-Stil zu konvertieren. Die Funktion, die es als Argument verwendet, benötigt nur einen zusätzlichen Parameter, der ihren Zustand darstellt, der in C-Bibliotheken oft als “void * user” bezeichnet wird, damit sie ihn beim Aufruf an den Funktionszeiger übergeben kann.

    – Kodoskop

    8. August 2017 um 14:05 Uhr

  • Diese Lösung funktioniert auch gut in Arduino, wo nur eine Teilmenge von C++ verfügbar ist.

    – hlovdal

    5. Februar um 13:41 Uhr

1646926214 157 C Lambda mit Captures als Funktionszeiger
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;
}

1644218467 32 C Einfacher Ruckgabewert von stdthread
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();
}

1646926215 539 C Lambda mit Captures als Funktionszeiger
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;
}

988330cookie-checkC++-Lambda mit Captures als Funktionszeiger

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy