web-dev-qa-db-fra.com

Lambda C ++ avec captures en tant que pointeur de fonction

Je jouais avec des lambdas C++ et leur conversion implicite en pointeurs de fonction. Mon exemple de départ les utilisait comme rappel pour la fonction ftw. Cela fonctionne comme prévu.

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

Après l'avoir modifié pour utiliser des captures:

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

J'ai eu l'erreur du compilateur:

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)’

Après quelques lectures. J'ai appris que les lambdas utilisant des captures ne peuvent pas être implicitement convertis en pointeurs de fonction.

Existe-t-il une solution à cela? Le fait qu'ils ne puissent pas être "implicitement" convertis signifie-t-il qu'ils peuvent être "explicitement" convertis? (J'ai essayé de lancer, sans succès). Quelle serait une manière propre de modifier l'exemple de travail afin que je puisse ajouter les entrées à un objet à l'aide de lambdas?.

82
duncan

Étant donné que la capture des lambdas doit préserver un état, il n'y a pas vraiment de "solution de contournement" simple, car ce sont pas juste des fonctions ordinaires. Le point sur un pointeur de fonction est qu'il pointe vers une seule fonction globale, et cette information n'a pas de place pour un état.

La solution de contournement la plus proche (qui élimine essentiellement l'état) est de fournir un type de variable globale accessible à partir de votre lambda/fonction. Par exemple, vous pouvez créer un objet functor traditionnel et lui donner une fonction membre statique qui fait référence à une instance unique (globale/statique).

Mais c'est en quelque sorte vaincre le but de capturer des lambdas.

39
Kerrek SB

Je viens de rencontrer ce problème.

Le code se compile correctement sans captures lambda, mais il existe une erreur de conversion de type avec la capture lambda.

La solution avec C++ 11 consiste à utiliser std::function (modifier: une autre solution qui ne nécessite pas de modifier la signature de la fonction est présentée après cet exemple). Vous pouvez aussi utiliser boost::function (qui fonctionne en fait beaucoup plus rapidement). Exemple de code - modifié pour qu'il compile, compilé avec 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;
}

Edit: J'ai dû revoir cela lorsque j'ai rencontré du code hérité où je ne pouvais pas modifier la signature de la fonction d'origine, mais j'avais encore besoin d'utiliser des lambdas. Une solution qui ne nécessite pas de modifier la signature de fonction de la fonction d'origine est ci-dessous:

#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

Les fonctions lambda sont très pratiques et réduisent un code. Dans mon cas, j'avais besoin de lambdas pour la programmation parallèle. Mais cela nécessite des pointeurs de capture et de fonction. Ma solution est là. Mais soyez prudent avec la portée des variables que vous avez capturées.

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

Exemple

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

Exemple avec une valeur de retour

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

MISE À JOUR

Version améliorée

Cela faisait un moment que le premier article sur C++ lambda avec captures en tant que pointeur de fonction avait été publié. Comme c'était utilisable pour moi et pour d'autres personnes, j'ai fait quelques améliorations.

La fonction standard api du pointeur C utilise la convention void fn (void * data). Par défaut, cette convention est utilisée et lambda doit être déclarée avec un argument void *.

Amélioration de la mise en œuvre

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

Conversion de lambda avec captures en un pointeur C

void (*f1)(void*) = Lambda::ptr(b);
f1(nullptr);
printf("%d\n", a);  // 101 

Peut également être utilisé de cette façon

auto f2 = Lambda::ptr(b);
f2(nullptr);
printf("%d\n", a); // 102

Si la valeur de retour doit être utilisée

int (*f3)(void*) = Lambda::ptr<int>(b);
printf("%d\n", f3(nullptr)); // 103

Et dans le cas où des données sont utilisées

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

En utilisant la méthode localement globale (statique), cela peut être fait comme suit

template <class F>
auto cify_no_args(F&& f) {
  static F fn = std::forward<F>(f);
  return [] {
    return fn();
  };
}

Supposons que nous ayons

void some_c_func(void (*callback)());

Donc, l'utilisation sera

some_c_func(cify_no_args([&] {
  // code
}));

Cela fonctionne parce que chaque lambda a une signature unique, donc la rendre statique n'est pas un problème. Suivre un wrapper générique avec un nombre variadique d'arguments et tout type de retour en utilisant la même méthode.

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

Et une utilisation similaire

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
Vladimir Talybin

Hehe - une question assez ancienne, mais quand même ...

#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

Il existe un moyen hackeux de convertir un lambda de capture en un pointeur de fonction, mais vous devez être prudent lors de son utilisation:

https://codereview.stackexchange.com/questions/79612/c-ifying-a-capturing-lambda

Votre code ressemblerait alors à ceci (avertissement: compilation du cerveau):

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

Ma solution, utilisez simplement un pointeur de fonction pour faire référence à un lambda statique.

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