124 votes

Lambda C++ avec des captures comme pointeur de fonction

Je jouais avec les lambdas C++ et leur conversion implicite en pointeurs de fonction. Mon exemple de départ était de les utiliser comme callback 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 les 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 obtenu 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 peut pas être converti implicitement à des pointeurs de fonction.

Existe-t-il une solution de contournement ? Le fait qu'ils ne puissent pas être convertis "implicitement" signifie-t-il qu'ils peuvent être convertis "explicitement" ? (J'ai essayé le casting, sans succès). Quelle serait la meilleure façon de modifier l'exemple de travail pour que je puisse ajouter les entrées à un objet en utilisant des lambdas ?

57voto

Jay West Points 171

Je viens de rencontrer ce problème.

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

La solution avec C++11 est d'utiliser std::function (edit : 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 également 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;
}

Editer : J'ai dû revenir sur ce point lorsque j'ai rencontré un code ancien pour lequel je ne pouvais pas modifier la signature de la fonction d'origine, mais qui nécessitait quand même l'utilisation de lambdas. Une solution qui ne nécessite pas de modifier la signature de la fonction originale est présentée 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;
}

55voto

Kerrek SB Points 194696

Comme les lambdas de capture doivent préserver un état, il n'y a pas vraiment de solution de contournement simple, puisqu'ils sont no des fonctions ordinaires. L'intérêt d'un pointeur de fonction est qu'il pointe vers une fonction unique et globale, et que cette information n'a pas de place pour un état.

La solution la plus proche (qui ne tient pas compte de l'état) est de fournir un type de variable globale à laquelle on accède à partir de votre lambda/fonction. Par exemple, vous pourriez créer un objet foncteur traditionnel et lui donner une fonction membre statique qui se réfère à une instance unique (globale/statique).

Mais cela va à l'encontre de l'objectif de la capture des lambdas.

29voto

Evgeny Karpov Points 132

ORIGINAL

Les fonctions lambda sont très pratiques et réduisent le code. Dans mon cas, j'avais besoin de lambdas pour la programmation parallèle. Mais cela nécessite la capture et les pointeurs de fonction. Ma solution est ici. Mais attention à 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 fait un moment que le premier message sur les lambdas C++ avec les captures comme pointeur de fonction a été posté. Comme c'était utilisable pour moi et d'autres personnes, j'ai fait quelques améliorations.

La fonction standard C pointer api utilise la convention void fn(void* data). Par défaut, cette convention est utilisée et la fonction 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 d'une 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 manière

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ù les 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

17voto

Vladimir Talybin Points 139

En utilisant la méthode localement globale (statique), on peut procéder 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)());

L'utilisation sera donc la suivante

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

Cela fonctionne parce que chaque lambda a une signature unique et que la rendre statique n'est donc pas un problème. Voici un wrapper générique avec un nombre variable d'arguments et n'importe quel type de retour 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 = 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));
}

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

4voto

egorse Points 61

Hehe - c'est une vieille question, mais tout de 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;
}

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X