Domanda Lambda C ++ con catture come puntatore a funzione


Stavo giocando con i lambda C ++ e la loro conversione implicita ai puntatori di funzioni. Il mio esempio di partenza li usava come callback per la funzione ftw. Funziona come previsto.

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

Dopo averlo modificato per usare le catture:

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

Ho ricevuto l'errore del compilatore:

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

Dopo qualche lettura. Ho imparato che lambda usa le catture non può essere convertito implicitamente per funzionare i puntatori.

C'è una soluzione per questo? Il fatto che non possano essere convertiti "implicitamente" significa che possono essere "esplicitamente" convertiti? (Ho provato a lanciare, senza successo). Quale sarebbe un modo pulito per modificare l'esempio di lavoro in modo che potessi aggiungere le voci a qualche oggetto usando lambdas ?.


71
2017-10-21 16:00


origine


risposte:


Dal momento che catturare lambda ha bisogno di preservare uno stato, non c'è in realtà una semplice "soluzione", dal momento che lo sono non solo funzioni ordinarie. Il punto su un puntatore di funzione è che punta a una singola funzione globale e questa informazione non ha spazio per uno stato.

La soluzione più vicina (che sostanzialmente elimina lo stato) consiste nel fornire un tipo di variabile globale a cui si accede dalla funzione lambda /. Ad esempio, è possibile creare un oggetto functor tradizionale e assegnargli una funzione membro statico che fa riferimento a un'istanza univoca (globale / statica).

Ma è una sorta di sconfiggere l'intero scopo di catturare lambda.


33
2017-10-21 16:05



Ho appena incontrato questo problema.

Il codice compila bene senza lambda capture, ma c'è un errore di conversione di tipo con lambda capture.

La soluzione con C ++ 11 è da utilizzare std::function (modifica: dopo questo esempio viene mostrata un'altra soluzione che non richiede la modifica della firma della funzione). Puoi anche usare boost::function (che funziona in realtà molto più veloce). Codice di esempio: modificato in modo che fosse compilato, compilato con 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;
}

Modificare: Ho dovuto rivisitare questo quando mi sono imbattuto in codice legacy in cui non potevo modificare la firma della funzione originale, ma avevo ancora bisogno di usare lambda. Di seguito è riportata una soluzione che non richiede la modifica della firma della funzione originale:

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

40
2018-06-25 19:03



Le funzioni Lambda sono molto convenienti e riducono un codice. Nel mio caso avevo bisogno di lambda per la programmazione parallela. Ma richiede cattura e puntatori di funzioni. La mia soluzione è qui. Ma fai attenzione alla portata delle variabili che hai catturato.

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

Esempio

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

Esempio con un valore di ritorno

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

4
2017-10-09 22:01



Usando il metodo localmente globale (statico) può essere fatto come segue

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

Supponiamo di averlo

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

Quindi l'uso sarà

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

Funziona perché ogni lambda ha una firma univoca, quindi renderla statica non è un problema. Seguendo un wrapper generico con numero variabile di argomenti e qualsiasi tipo di ritorno che utilizza lo stesso metodo.

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

E un uso simile

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

3
2018-01-21 15:38



Hehe - una domanda piuttosto vecchia, ma ancora ...

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

2
2018-06-10 06:28



C'è un modo hacker per convertire un lambda in un puntatore a funzione, ma devi fare attenzione quando lo usi:

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

Il tuo codice sarebbe quindi simile a questo (avvertenza: compilazione del cervello):

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
2018-02-05 07:32



Trovato una risposta qui: http://meh.schizofreni.co/programming/magic/2013/01/23/function-pointer-from-lambda.html

Si converte lambda pointer a void* e riconvertire quando necessario.

  1. a void*:

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

  2. a partire dal void*:

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


-1
2018-01-21 04:52