C++ lambda avec captures comme pointeur de fonction

je jouais avec C++ lambdas 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 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 lambdas utilisant des captures ne peut pas être converti implicitement en des pointeurs de fonction.

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

77
demandé sur duncan 2011-10-21 20:00:26

7 réponses

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

la solution la plus proche (qui écarte essentiellement l'état) est de fournir un certain type de variable globale qui est accessible à partir de votre lambda/fonction. Pour par exemple, vous pouvez créer un objet fonctionnel traditionnel et lui donner une fonction de membre statique qui se réfère à une instance unique (globale/statique).

mais ça va à l'encontre du but de la capture de lambdas.

35
répondu Kerrek SB 2011-10-21 16:05:16

je viens de rencontrer ce problème.

le code compile fine without lambda captures, mais il y a une erreur de conversion de type avec lambda capture.

Solution avec C++11 est d'utiliser std::function (éditer: une autre solution qui ne nécessite pas de modifier la signature de la fonction est montré après cet exemple). Vous pouvez également utiliser boost::function (qui fonctionne en fait beaucoup plus rapidement). Exemple de code-modifié pour qu'il soit compilé, 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û revisiter cela quand je suis tombé sur le code d'héritage où je ne pouvais pas modifier la signature de la fonction originale, mais j'avais encore besoin d'utiliser lambdas. Une solution qui ne nécessite pas de modifier la signature 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;
}
42
répondu Jay West 2018-02-03 18:52:30

ORIGINAL

les fonctions Lambda sont très pratiques et réduisent le code. Dans mon cas, j'avais besoin lambdas pour la programmation parallèle. Mais cela nécessite des points de capture et de fonctionnement. Ma solution est ici. Mais faites attention 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 longtemps que le premier post sur C++ lambda avec des captures comme pointeur de fonction n'avait pas été publié. Comme Il n'était utilisable pour moi et d'autres personnes j'ai fait quelques améliorations.

fonction Standard C pointeur api utilise void fn(void* data) de la convention. 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 lambda avec captures En pointeur C

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

peut aussi être utilisé de cette façon

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

dans le cas où 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
8
répondu Evgeny Karpov 2018-09-03 11:28:20

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

template <class T>
auto wrap(T t) {
  static T fn = t;
  return [] { fn(); };
}

supposons que nous ayons

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

donc l'usage sera

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

cela fonctionne parce que chaque lambda a une signature unique donc le rendre statique n'est pas un problème. Suivant un wrapper générique avec un nombre variadique d'arguments et n'importe quel type de retour utilisant la même méthode.

template <class T>
struct lambda_traits : lambda_traits<decltype(&T::operator())>
{ };

template <class T, class R, class... Args>
struct lambda_traits<R(T::*)(Args...) const> {
    typedef R (*pointer)(Args...);

    static pointer cify(T t) {
        static T fn = t;
        return [](Args... args) {
            return fn(args...);
        };
    }
};

template <class T>
inline typename lambda_traits<T>::pointer cify(T t) {
    return lambda_traits<T>::cify(t);
}

et usages similaires

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;
}));
4
répondu Vladimir Talybin 2018-03-19 10:29:16

Hehe-une vieille question, 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;
}
3
répondu egorse 2015-06-10 06:28:17

il y a une façon hackish de convertir un lambda de capture en pointeur de fonction, mais vous devez être prudent lorsque vous l'utilisez:

/q/how-to-save-a-public-html-page-with-all-media-and-preserve-structure-64214/"/etc", callback, 1); for (auto entry : entries ) { cout << entry << endl; } return ret; }

0
répondu user1095108 2017-04-13 12:40:37

Trouvé une réponse ici: http://meh.schizofreni.co/programming/magic/2013/01/23/function-pointer-from-lambda.html

il convertit lambda pointer en void* et de convertir en arrière si nécessaire.

  1. à void* :

    auto voidfunction = new decltype(to_function (lambda)) (to_function(lambda));

  2. de void* :

    fonction auto = static_cast< std::function*>( voidfunction);

-1
répondu jesse 2015-01-21 05:31:31