Passando a catturare lambda come puntatore a funzione


210

È possibile passare una funzione lambda come puntatore a funzione? In tal caso, devo fare qualcosa in modo errato perché visualizzo un errore di compilazione.

Considera il seguente esempio

using DecisionFn = bool(*)();

class Decide
{
public:
    Decide(DecisionFn dec) : _dec{dec} {}
private:
    DecisionFn _dec;
};

int main()
{
    int x = 5;
    Decide greaterThanThree{ [x](){ return x > 3; } };
    return 0;
}

Quando provo a compilare questo , ottengo il seguente errore di compilazione:

In function 'int main()':
17:31: error: the value of 'x' is not usable in a constant expression
16:9:  note: 'int x' is not const
17:53: error: no matching function for call to 'Decide::Decide(<brace-enclosed initializer list>)'
17:53: note: candidates are:
9:5:   note: Decide::Decide(DecisionFn)
9:5:   note: no known conversion for argument 1 from 'main()::<lambda()>' to 'DecisionFn {aka bool (*)()}'
6:7:   note: constexpr Decide::Decide(const Decide&)
6:7:   note: no known conversion for argument 1 from 'main()::<lambda()>' to 'const Decide&'
6:7:   note: constexpr Decide::Decide(Decide&&)
6:7:   note: no known conversion for argument 1 from 'main()::<lambda()>' to 'Decide&&'

Questo è un diavolo di un messaggio di errore da digerire, ma penso che ciò che sto uscendo da esso è che il lambda non può essere trattato come un constexprquindi quindi non posso passarlo come puntatore a funzione? Ho provato anche a fare xconst, ma questo non sembra aiutare.


34
lambda può decadere per funzionare puntatore solo se non catturano nulla.
Jarod42,


Per i posteri, il post di blog sopra collegato ora vive su devblogs.microsoft.com/oldnewthing/20150220-00/?p=44623
warrenm

Risposte:


205

Un lambda può essere convertito in un puntatore a funzione solo se non acquisisce, dalla bozza della sezione standard C ++ 11 5.1.2 [expr.prim.lambda] dice ( enfasi mia ):

Il tipo di chiusura per un'espressione lambda senza acquisizione lambda ha una funzione di conversione const non esplicita pubblica non virtuale in puntatore a funzione con gli stessi parametri e tipi restituiti dell'operatore di chiamata della funzione del tipo di chiusura. Il valore restituito da questa funzione di conversione deve essere l'indirizzo di una funzione che, se richiamata, ha lo stesso effetto di invocare l'operatore di chiamata della funzione del tipo di chiusura.

Nota, cppreference copre anche questo nella loro sezione sulle funzioni Lambda .

Quindi le seguenti alternative funzionerebbero:

typedef bool(*DecisionFn)(int);

Decide greaterThanThree{ []( int x ){ return x > 3; } };

e così sarebbe questo:

typedef bool(*DecisionFn)();

Decide greaterThanThree{ [](){ return true ; } };

e come sottolinea 5gon12eder , puoi anche usare std::function, ma nota che std::functionè pesante , quindi non è un compromesso senza costi.


2
Nota a margine: una soluzione comune utilizzata da C stuff è passare a void*come unico parametro. Normalmente viene chiamato "puntatore utente". È anche relativamente leggero, ma tende a richiedere mallocun po 'di spazio.
Finanzia la causa di Monica il

94

La risposta di Shafik Yaghmour spiega correttamente perché la lambda non può essere passata come puntatore a funzione se ha una cattura. Vorrei mostrare due semplici soluzioni per il problema.

  1. Utilizzare al std::functionposto dei puntatori a funzioni non elaborate.

    Questa è una soluzione molto pulita. Si noti tuttavia che include un sovraccarico aggiuntivo per la cancellazione del tipo (probabilmente una chiamata di funzione virtuale).

    #include <functional>
    #include <utility>
    
    struct Decide
    {
      using DecisionFn = std::function<bool()>;
      Decide(DecisionFn dec) : dec_ {std::move(dec)} {}
      DecisionFn dec_;
    };
    
    int
    main()
    {
      int x = 5;
      Decide greaterThanThree { [x](){ return x > 3; } };
    }
  2. Usa un'espressione lambda che non catturi nulla.

    Poiché il tuo predicato è in realtà solo una costante booleana, ciò che segue aggirerebbe rapidamente il problema attuale. Vedi questa risposta per una buona spiegazione del perché e di come funziona.

    // Your 'Decide' class as in your post.
    
    int
    main()
    {
      int x = 5;
      Decide greaterThanThree {
        (x > 3) ? [](){ return true; } : [](){ return false; }
      };
    }

4
@TC Vedi questa domanda per i dettagli sul perché funziona
Shafik Yaghmour,

Tieni presente che in generale, se conosci i dati di acquisizione in fase di compilazione, puoi convertirli per digitare i dati e poi sei tornato ad avere un lambda senza acquisizione: vedi questa risposta che ho appena scritto in un'altra domanda (grazie a @ La risposta di 5gon12eder qui).
dan-man,

Allora l'oggetto non dovrebbe avere una durata maggiore rispetto alla funzione puntatore? Vorrei usarlo per glutReshapeFunc.
ar2015

non consiglio questo suggerimento, cose che tendono a funzionare magicamente, introducono nuovi errori. e pratiche che accompagnano quegli errori. se vuoi usare std :: function, dovresti vedere tutti i tipi di modi in cui std :: function può essere usato. perché in qualche modo forse qualcosa che non vuoi.
TheNegative

1
Questo non risponde alla domanda. Se uno potesse usare std::functiono un lambda - perché non dovrebbero? Per lo meno è una sintassi più leggibile. Di solito è necessario utilizzare un puntatore a funzione per interagire con le librerie C (in realtà, con qualsiasi libreria esterna) e assicurarsi di non poterlo modificare per accettare una std :: function o lambda.
Ciao Angelo

40

Le espressioni lambda, anche quelle catturate, possono essere gestite come puntatore a funzione (puntatore alla funzione membro).

È complicato perché un'espressione lambda non è una funzione semplice. In realtà è un oggetto con un operatore ().

Quando sei creativo, puoi usarlo! Pensa a una classe "funzione" nello stile di std :: function. Se si salva l'oggetto è anche possibile utilizzare il puntatore a funzione.

Per utilizzare il puntatore a funzione, è possibile utilizzare quanto segue:

int first = 5;
auto lambda = [=](int x, int z) {
    return x + z + first;
};
int(decltype(lambda)::*ptr)(int, int)const = &decltype(lambda)::operator();
std::cout << "test = " << (lambda.*ptr)(2, 3) << std::endl;

Per creare una classe che può iniziare a funzionare come una "funzione std :: function", è innanzitutto necessario disporre di una classe / struttura in grado di memorizzare l'oggetto e il puntatore alla funzione. Inoltre è necessario un operatore () per eseguirlo:

// OT => Object Type
// RT => Return Type
// A ... => Arguments
template<typename OT, typename RT, typename ... A>
struct lambda_expression {
    OT _object;
    RT(OT::*_function)(A...)const;

    lambda_expression(const OT & object)
        : _object(object), _function(&decltype(_object)::operator()) {}

    RT operator() (A ... args) const {
        return (_object.*_function)(args...);
    }
};

Con questo ora puoi eseguire lambda catturati e non catturati, proprio come stai usando l'originale:

auto capture_lambda() {
    int first = 5;
    auto lambda = [=](int x, int z) {
        return x + z + first;
    };
    return lambda_expression<decltype(lambda), int, int, int>(lambda);
}

auto noncapture_lambda() {
    auto lambda = [](int x, int z) {
        return x + z;
    };
    return lambda_expression<decltype(lambda), int, int, int>(lambda);
}

void refcapture_lambda() {
    int test;
    auto lambda = [&](int x, int z) {
        test = x + z;
    };
    lambda_expression<decltype(lambda), void, int, int>f(lambda);
    f(2, 3);

    std::cout << "test value = " << test << std::endl;
}

int main(int argc, char **argv) {
    auto f_capture = capture_lambda();
    auto f_noncapture = noncapture_lambda();

    std::cout << "main test = " << f_capture(2, 3) << std::endl;
    std::cout << "main test = " << f_noncapture(2, 3) << std::endl;

    refcapture_lambda();

    system("PAUSE");
    return 0;
}

Questo codice funziona con VS2015

Aggiornamento 04.07.17:

template <typename CT, typename ... A> struct function
: public function<decltype(&CT::operator())(A...)> {};

template <typename C> struct function<C> {
private:
    C mObject;

public:
    function(const C & obj)
        : mObject(obj) {}

    template<typename... Args> typename 
    std::result_of<C(Args...)>::type operator()(Args... a) {
        return this->mObject.operator()(a...);
    }

    template<typename... Args> typename 
    std::result_of<const C(Args...)>::type operator()(Args... a) const {
        return this->mObject.operator()(a...);
    }
};

namespace make {
    template<typename C> auto function(const C & obj) {
        return ::function<C>(obj);
    }
}

int main(int argc, char ** argv) {
   auto func = make::function([](int y, int x) { return x*y; });
   std::cout << func(2, 4) << std::endl;
   system("PAUSE");
   return 0;
}

Wow, è fantastico! Quindi potremmo semplicemente usare i puntatori interni della classe lambda (all'operatore della funzione membro ()) per invocare lambda memorizzati in una classe wrapper !! SORPRENDENTE!! Perché allora abbiamo mai bisogno della funzione std ::? Ed è possibile fare in modo che lambda_expression <decltype (lambda), int, int, int> deduca automaticamente / questi parametri "int" direttamente dallo stesso lambda passato?
Barney,

2
Ho aggiunto una versione breve del mio codice. questo dovrebbe funzionare con una semplice auto f = make :: function (lambda); Ma sono abbastanza sicuro che troverai molte situazioni in cui il mio codice non funzionerà. std :: function è molto più ben costruita di così e dovrebbe essere la scelta giusta quando lavori. Questo qui è per l'educazione e l'uso personale.
Noxxer,

14
Questa soluzione prevede la chiamata del lambda tramite operator()un'implementazione, quindi se la sto leggendo nel modo giusto non penso che funzionerebbe per chiamare il lambda usando un puntatore a funzione in stile C , vero? Questa è la domanda originale.
Remy Lebeau,

13
Hai affermato che lambdas può essere gestito come puntatore a funzione, cosa che non hai fatto. Hai creato un altro oggetto per contenere una lambda, che non fa nulla, avresti potuto semplicemente usare la lambda originale.
Passer Entro il

9
Non si tratta di "passare a catturare lambda come puntatore a funzione". Questo è "passare catturare lambda come un oggetto che contiene un puntatore a funzione tra le altre cose". C'è un mondo di differenza.
n. 'pronomi' m.

15

Catturare lambda non può essere convertito in puntatori a funzione, come sottolineato da questa risposta .

Tuttavia, spesso è piuttosto problematico fornire un puntatore a una API che ne accetta solo una. Il metodo più spesso citato per farlo è quello di fornire una funzione e chiamare un oggetto statico con esso.

static Callable callable;
static bool wrapper()
{
    return callable();
}

Questo è noioso. Portiamo avanti questa idea e automatizziamo il processo di creazione wrappere rendiamo la vita molto più semplice.

#include<type_traits>
#include<utility>

template<typename Callable>
union storage
{
    storage() {}
    std::decay_t<Callable> callable;
};

template<int, typename Callable, typename Ret, typename... Args>
auto fnptr_(Callable&& c, Ret (*)(Args...))
{
    static bool used = false;
    static storage<Callable> s;
    using type = decltype(s.callable);

    if(used)
        s.callable.~type();
    new (&s.callable) type(std::forward<Callable>(c));
    used = true;

    return [](Args... args) -> Ret {
        return Ret(s.callable(std::forward<Args>(args)...));
    };
}

template<typename Fn, int N = 0, typename Callable>
Fn* fnptr(Callable&& c)
{
    return fnptr_<N>(std::forward<Callable>(c), (Fn*)nullptr);
}

E usalo come

void foo(void (*fn)())
{
    fn();   
}

int main()
{
    int i = 42;
    auto fn = fnptr<void()>([i]{std::cout << i;});
    foo(fn);  // compiles!
}

Vivere

Questo sta essenzialmente dichiarando una funzione anonima ad ogni occorrenza di fnptr.

Si noti che le invocazioni di fnptrsovrascrivono i callablecallable dati precedentemente scritti dello stesso tipo. Rimediamo a questo, in una certa misura, con il intparametro N.

std::function<void()> func1, func2;
auto fn1 = fnptr<void(), 1>(func1);
auto fn2 = fnptr<void(), 2>(func2);  // different function

forzare la dichiarazione dell'intero N sarebbe un modo elegante per ricordare il client per evitare di sovrascrivere i puntatori di funzione al momento della compilazione.
fiorentino il

2

Una scorciatoia per usare un lambda con come puntatore a funzione C è questa:

"auto fun = +[](){}"

Utilizzo di Curl come esempio ( informazioni di debug per curl )

auto callback = +[](CURL* handle, curl_infotype type, char* data, size_t size, void*){ //add code here :-) };
curl_easy_setopt(curlHande, CURLOPT_VERBOSE, 1L);
curl_easy_setopt(curlHande,CURLOPT_DEBUGFUNCTION,callback);

3
Quella lambda non ha una cattura. Il problema del PO è l'acquisizione, non dovendo dedurre il tipo di puntatore a funzione (che è ciò che il +trucco ti procura).
Sneftel,

2

Sebbene l'approccio del modello sia intelligente per vari motivi, è importante ricordare il ciclo di vita della lambda e delle variabili acquisite. Se verrà utilizzata una qualsiasi forma di un puntatore lambda e la lambda non è una continuazione verso il basso, è necessario utilizzare solo una copia [=] lambda. Vale a dire, anche in questo caso, catturare un puntatore a una variabile nello stack è UNSAFE se la durata di questi puntatori catturati (stack unfind) è inferiore alla durata della lambda.

Una soluzione più semplice per catturare un lambda come puntatore è:

auto pLamdba = new std::function<...fn-sig...>([=](...fn-sig...){...});

per esempio, new std::function<void()>([=]() -> void {...}

Ricorda solo più tardi, delete pLamdbaquindi assicurati di non perdere la memoria lambda. Segreto da realizzare qui è che lambda può catturare lambda (chiediti come funziona) e anche che per std::functionfunzionare genericamente l'implementazione lambda deve contenere sufficienti informazioni interne per fornire accesso alla dimensione dei dati lambda (e acquisiti) ( ecco perché deletedovrebbe funzionare [eseguendo distruttori di tipi catturati]).


Perché preoccuparsi della funzione new- std :: memorizza già la lambda nell'heap ed evita di dover ricordare di aver chiamato delete.
Chris Dodd

0

Non una risposta diretta, ma una leggera variazione per utilizzare il modello di modello "functor" per nascondere le specifiche del tipo lambda e mantenere il codice semplice e gradevole.

Non ero sicuro di come volessi usare la classe decide, quindi ho dovuto estendere la classe con una funzione che la utilizza. Vedi l'esempio completo qui: https://godbolt.org/z/jtByqE

La forma base della tua classe potrebbe apparire così:

template <typename Functor>
class Decide
{
public:
    Decide(Functor dec) : _dec{dec} {}
private:
    Functor _dec;
};

Dove passi il tipo di funzione come parte del tipo di classe usato come:

auto decide_fc = [](int x){ return x > 3; };
Decide<decltype(decide_fc)> greaterThanThree{decide_fc};

Ancora una volta, non ero sicuro del motivo per cui stai acquisendo x, aveva più senso (per me) avere un parametro che passi alla lambda) in modo da poter usare come:

int result = _dec(5); // or whatever value

Vedi il link per un esempio completo


-2

Come è stato menzionato dagli altri, è possibile sostituire la funzione Lambda anziché il puntatore alla funzione. Sto usando questo metodo nella mia interfaccia C ++ con il solutore F77 ODE RKSUITE.

//C interface to Fortran subroutine UT
extern "C"  void UT(void(*)(double*,double*,double*),double*,double*,double*,
double*,double*,double*,int*);

// C++ wrapper which calls extern "C" void UT routine
static  void   rk_ut(void(*)(double*,double*,double*),double*,double*,double*,
double*,double*,double*,int*);

//  Call of rk_ut with lambda passed instead of function pointer to derivative
//  routine
mathlib::RungeKuttaSolver::rk_ut([](double* T,double* Y,double* YP)->void{YP[0]=Y[1]; YP[1]= -Y[0];}, TWANT,T,Y,YP,YMAX,WORK,UFLAG);
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.