Lambda C ++ con acquisizioni come puntatore a funzione


94

Stavo giocando con i lambda C ++ e la loro conversione implicita in puntatori a funzione. Il mio esempio iniziale li stava usando 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 utilizzare le acquisizioni:

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 un po 'di lettura. Ho imparato che i lambda che utilizzano le acquisizioni non possono essere convertiti implicitamente in puntatori a funzione.

C'è una soluzione alternativa per questo? Il fatto che non possano essere convertiti "implicitamente" significa che possono essere convertiti "esplicitamente"? (Ho provato il casting, senza successo). Quale sarebbe un modo pulito per modificare l'esempio funzionante in modo da poter aggiungere le voci a qualche oggetto usando lambda ?.


Che compilatore stai usando? è VS10?
Ramon Zarazua B.

gcc versione 4.6.1 20110801 [gcc-4_6-branch revision 177033] (SUSE Linux)
duncan

4
Di solito, il modo in C di passare lo stato ai callback viene eseguito tramite un argomento extra al callback (solitamente di tipo void *). Se la libreria che stai utilizzando consente questo argomento aggiuntivo, troverai una soluzione alternativa. Altrimenti, non hai modo di ottenere in modo pulito ciò che vuoi fare.
Alexandre C.

Sì. Mi rendo conto che l'api di ftw.he nftw.h è difettoso. Proverò fts.h
duncan

1
Grande! /usr/include/fts.h:41:3: errore: #error "<fts.h> non può essere utilizzato con -D_FILE_OFFSET_BITS == 64"
duncan

Risposte:


47

Dal lambda cattura necessità di preservare uno stato, non c'è davvero un semplice "workaround", dal momento che sono non solo le funzioni ordinarie. Il punto su un puntatore a funzione è che punta a una singola funzione globale e questa informazione non ha spazio per uno stato.

La soluzione alternativa più vicina (che essenzialmente elimina lo stato) è fornire un tipo di variabile globale a cui si accede dalla funzione lambda /. Ad esempio, potresti creare un oggetto funtore tradizionale e assegnargli una funzione membro statica che fa riferimento a un'istanza unica (globale / statica).

Ma questo è un po 'sconfiggere l'intero scopo di catturare lambda.


3
Una soluzione più pulita è avvolgere il lambda all'interno di un adattatore, assumendo che il puntatore a funzione abbia un parametro di contesto.
Raymond Chen

4
@ RaymondChen: Beh, se sei libero di definire come deve essere utilizzata la funzione, allora sì, è un'opzione. Anche se in quel caso sarebbe ancora più semplice rendere il parametro un argomento del lambda stesso!
Kerrek SB

3
@ KerrekSB mette le variabili globali in a namespacee le contrassegna come thread_local, questo è l' ftwapproccio che ho scelto per risolvere qualcosa di simile.
Kjell Hedström

"un puntatore a una funzione punta a una singola funzione globale e questa informazione non ha spazio per uno stato." -> Come diavolo possono linguaggi come Java ottenere questo risultato? Beh, ovviamente, perché quella singola funzione globale viene creata in fase di esecuzione e incorpora lo stato (o meglio il riferimento ad essa) nel proprio codice. Questo è il punto - ci dovrebbe non essere una sola, funzione globale ma molteplici funzioni globali - uno per ogni lambda volta che viene utilizzato in fase di esecuzione. Non c'è davvero NULLA in C ++ che lo faccia? (Pensavo che std :: function fosse fatto esattamente per quel singolo scopo)
Dexter

1
@Dexter: errr .. la risposta breve è no, la risposta lunga implica il sovraccarico dell'operatore. Indipendentemente da ciò, il mio punto è valido. Java è un linguaggio diverso che non è lo stesso di C ++; Java non ha puntatori (o operatori di chiamata sovraccaricabili) e il confronto non funziona bene.
Kerrek SB

47

Mi sono appena imbattuto in questo problema.

Il codice viene compilato correttamente senza acquisizioni lambda, ma è presente un errore di conversione del tipo con l'acquisizione lambda.

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

Modifica: ho dovuto rivisitarlo 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 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;
}

72
No, questa non dovrebbe essere la risposta accettata. Il punto non cambia ftwper prendere al std::functionposto di un puntatore a funzione ...
Gregory Pakosz

La seconda soluzione proposta all'interno di questa risposta affronta la preoccupazione di @ gregory-pakosz preservando la firma originale, ma non è ancora eccezionale perché introduce lo stato globale. Se ftwavesse un argomento void * userdata, preferirei la risposta da @ evgeny-karpov.
prideout

@prideout concordato - Neanche a me piace lo stato globale. Sfortunatamente, supponendo che la firma di ftw non possa essere modificata e dato che non ha void * userdata, lo stato deve essere memorizzato da qualche parte. Ho riscontrato questo problema utilizzando una libreria di terze parti. Questo funzionerà bene fintanto che la libreria non acquisisce il callback e lo utilizza in seguito, nel qual caso la variabile globale agisce semplicemente come un parametro aggiuntivo nello stack di chiamate. Se la firma di ftw può essere modificata, preferirei usare std :: function invece di void * userdata.
Jay West

1
questa è una soluzione estremamente complicata e utile, @Gregory dovrei dirti "funziona".
fiorentino il

16

ORIGINALE

Le funzioni Lambda sono molto comode e riducono il codice. Nel mio caso avevo bisogno di lambda per la programmazione parallela. Ma richiede l'acquisizione e puntatori a funzioni. La mia soluzione è qui. Ma fai attenzione all'ambito 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);

AGGIORNARE

Versione migliorata

È passato un po 'di tempo da quando è stato pubblicato il primo post su lambda C ++ con acquisizioni come puntatore a funzione. Dato che era utilizzabile per me e altre persone, ho fatto dei miglioramenti.

L'api del puntatore C della funzione standard utilizza la convenzione void fn (void * data). Per impostazione predefinita, viene utilizzata questa convenzione e lambda deve essere dichiarato con un argomento void *.

Implementazione migliorata

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

Conversione di lambda con acquisizioni in un puntatore C.

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

Può essere utilizzato anche in questo modo

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

Nel caso in cui debba essere utilizzato il valore di ritorno

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

E nel caso in cui vengano utilizzati i dati

auto b2 = [&](void* data) {return *(int*)(data) + a;};
int (*f4)(void*) = Lambda::ptr<int>(b2);
int data = 5;
printf("%d\n", f4(&data)); // 108

3
Questa è sicuramente la soluzione più conveniente che ho visto per convertire un lambda in un puntatore a funzione in stile C. La funzione che lo prende come argomento avrà solo bisogno di un parametro extra che ne rappresenti lo stato, spesso chiamato "void * user" nelle librerie C, in modo che possa passarlo al puntatore a funzione quando lo chiama.
Codoscope

10

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

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

Supponiamo di averlo fatto

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

Quindi l'utilizzo sarà

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

Questo funziona perché ogni lambda ha una firma univoca, quindi renderlo statico non è un problema. Di seguito è riportato un wrapper generico con un numero variabile di argomenti e qualsiasi tipo restituito utilizzando lo stesso metodo.

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 lambda_traits<F>::pointer cify(F&& f) {
    return lambda_traits<F>::cify(std::forward<F>(f));
}

E un utilizzo 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;
}));

1
tieni presente che questo copierà la chiusura (quando ottieni il ptr) + args (quando chiami). Altrimenti, è una soluzione elegante
Ivan Sanz-Carasa


1
@ IvanSanz-Carasa Grazie per avermelo fatto notare. I tipi di chiusura non sono CopyAssignable, ma i funtori lo sono. Quindi hai ragione, è meglio usare l'inoltro perfetto qui. Per gli argomenti d'altra parte non possiamo fare molto perché il semplice C non supporta i riferimenti universali, ma almeno possiamo inoltrare i valori al nostro lambda. Ciò potrebbe salvare una copia in più. Ha modificato il codice.
Vladimir Talybin

@RiaD Sì, perché lambda un'istanza statica qui dovrai invece acquisire per riferimento, ad esempio invece di =usarla &inel tuo ciclo for.
Vladimir Talybin

5

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

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

0

C'è un modo hacker per convertire un lambda di cattura in un puntatore a funzione, ma devi stare attento quando lo usi:

/codereview/79612/c-ifying-a-capturing-lambda

Il tuo codice sarebbe quindi simile a questo (avviso: 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

La mia soluzione, usa solo un puntatore a funzione per fare riferimento a un lambda statico.

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

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.