webentwicklung-frage-antwort-db.com.de

C ++ Lambda mit Captures als Funktionszeiger

Ich habe mit C++ - Lambdas und ihrer impliziten Konvertierung in Funktionszeiger gespielt. Mein Startbeispiel war, sie als Rückruf für die ftw-Funktion zu verwenden. Das 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 bekommen:

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 gelernt, dass Lambdas mit Captures kann nicht implizit konvertiert werden Zeiger funktionieren.

Gibt es eine Problemumgehung dafür? Bedeutet die Tatsache, dass sie nicht "implizit" konvertiert werden können, dass sie "explizit" konvertiert werden können? (Ich habe es ohne Erfolg versucht). Was wäre eine saubere Möglichkeit, das Arbeitsbeispiel so zu ändern, dass ich die Einträge mithilfe von Lambdas an ein Objekt anhängen könnte?.

82
duncan

Da das Erfassen von Lambdas einen Zustand bewahren muss, gibt es keine einfache "Problemumgehung", da es sich um nicht gewöhnliche Funktionen handelt. Der Punkt bei einem Funktionszeiger ist, dass er auf eine einzelne globale Funktion verweist und diese Informationen keinen Platz für einen Zustand haben.

Die nächstgelegene Problemumgehung (die im Wesentlichen den Zustand verwirft) besteht darin, eine Art globale Variable bereitzustellen, auf die von Ihrem Lambda/Ihrer Funktion aus zugegriffen wird. Sie können beispielsweise ein herkömmliches Funktionsobjekt erstellen und ihm eine statische Elementfunktion zuweisen, die auf eine eindeutige (globale/statische) Instanz verweist.

Aber das ist eine Art Niederlage gegen den gesamten Zweck, Lambdas einzufangen.

39
Kerrek SB

Ich bin gerade auf dieses Problem gestoßen.

Der Code kann ohne Lambda-Erfassung problemlos kompiliert werden, bei der Lambda-Erfassung tritt jedoch ein Typkonvertierungsfehler auf.

Lösung mit C++ 11 ist die Verwendung von std::function (Bearbeiten: Nach diesem Beispiel wird eine andere Lösung angezeigt, bei der die Funktionssignatur nicht geändert werden muss.) Sie können auch boost::function (was eigentlich deutlich schneller läuft). Beispielcode - geändert, damit er kompiliert wird, 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 älteren Code stieß, in dem ich die ursprüngliche Funktionssignatur nicht ändern konnte, aber dennoch Lambdas verwenden musste. Eine Lösung, bei der die Funktionssignatur der ursprünglichen Funktion nicht geändert werden muss, ist im Folgenden aufgeführt:

#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;
}
46
Jay West

ORIGINAL

Lambda-Funktionen sind sehr praktisch und reduzieren einen Code. In meinem Fall brauchte ich Lambdas für die parallele Programmierung. Dafür sind jedoch Erfassungs- und Funktionszeiger erforderlich. 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 einem Rückgabewert

int a = 100;
auto b = [&]() {return a;};
int (*fp)(void*) = lambda_ptr<int>(b);
fp(&b);

UPDATE

Verbesserte Version

Es ist eine Weile her, dass 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 mich verbessert.

Die Standardfunktion C pointer api 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;
    }
};

Exapmle

int a = 100;
auto b = [&](void*) {return ++a;};

Umwandlung 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
11
Evgeny Karpov

Mit der lokal globalen (statischen) Methode kann wie folgt vorgegangen 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. Nach einem generischen Wrapper mit variabler Anzahl von Argumenten und einem beliebigen Rückgabetyp mit derselben Methode.

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

Hehe - eine ziemlich alte Frage, aber immer noch ...

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

Es gibt eine hackige Möglichkeit, 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 (Warnung: Gehirn kompilieren):

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;
}
0
user1095108

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();
}
0
Zhang