Puntatori di funzioni C con lambda C ++ 11

Quindi sto provando a scrivere una funzione di integrazione da utilizzare con lambda c ++ 11. Il codice ha un aspetto simile al seguente:

double Integrate(std::function func, double a,double b,std::vector & params) { gsl_integration_workspace * w = gsl_integration_workspace_alloc (1000); gsl_function F; F.function =func; F.params = (void*)&params; double error,result; gsl_integration_qag (&F, a, b, 0, 1e-7, 1000,GSL_INTEG_GAUSS61,w, &result, &error); gsl_integration_workspace_free (w); return result; } void Another_function() { //... Integrate([](double a,void* param) { return ((vector *)params)->at(0)*a+((vector *)params)->at(1); } ,0,3,{2,3}); } 

Cercando di compilarlo, il compilatore dice:

 error: cannot convert 'std::function' to 'double (*)(double, void*)' in assignment 

sulla linea

 F.function =func; 

Ma se scrivo:

 F.function =[](double a,void* param) { return ((std::vector *)param)->at(0)*a+((std::vector *)param)->at(1); }; 

Compila e funziona bene. Come dovrei risolvere questo?

    L’uso di un vuoto * è tipico delle interfacce di callback C per passare un po ‘di “stato” alla funzione. Tuttavia, std :: function non ha bisogno di questo perché std :: function supporta “stateful functions”. Quindi, potresti fare qualcosa del genere:

     double Integrate( std::function func, double a, double b) { typedef std::function fun_type; ::: F.function = [](double x, void* p){ return (*static_cast(p))(x); }; F.params = &func; ::: } 

    e memorizzare un riferimento al vettore di parametri come parte del functor che sarà incapsulato nell’object std :: function o fare qualcosa di simile a questo:

     void Another_function() { double m = 2; double b = 3; auto func = [&](double x){return m*x+b}; auto r1 = Integrate(func,0,3); ::: } 

    Tuttavia, questa soluzione userebbe piuttosto molti riferimenti indiretti. GSL invocherà il tuo lambda. Il tuo lambda invocerebbe lo std :: function <> :: operator () che a sua volta avrebbe attivato una sorta di funzione virtuale che viene utilizzata per la cancellazione del tipo che a sua volta invocherà il calcolo effettivo.

    Quindi, se ti interessano le prestazioni, potresti sbarazzarti di un paio di livelli, in particolare std :: function. Ecco un altro approccio con un modello di funzione:

     template double Integrate( Func func, double a, double b) { ::: F.function = [](double x, void* p)->double{ return (*static_cast(p))(x); }; F.params = &func; ::: } 

    Suppongo che preferirei questo sulla soluzione std :: function.

    Sembra che la libreria gsl richieda un puntatore a funzione. Un lambda che non cattura può essere convertito in un puntatore a funzione. Qualsiasi lambda può essere convertita in una std::function . Ma una std::function non può essere convertita in un puntatore a funzione.

    Potresti provare:

     struct functor_and_params { std::function f; void* params; static double invoke(double x, void* ptr) { functor_and_params& f_and_p = *reinterpret_cast(ptr); return f_and_p.f(x, f_and_p.params); } }; double Integrate(std::function func, double a,double b,std::vector & params) { functor_and_params f_and_p{ func, &params }; gsl_function F; F.function = &functor_and_params::invoke; F.params = &f_and_p; //... } 

    Una std::function<> non può essere convertita in un puntatore di funzione. std::function<> sono oggetti funzione che possono potenzialmente contenere lo stato mentre le funzioni regolari sono stateless (in pratica, potresti potenzialmente avere variabili static , ma questa è una cosa diversa).

    D’altra parte, lambda stateless può essere convertito in un puntatore a funzione, quindi potresti potenzialmente cambiare la firma della tua funzione per prendere direttamente un puntatore a funzione e il lambda verrà convertito:

     double Integrate(double(*func)(double,void*), double a, double b, std::vector & params) // !!! std::vector p{2,3}; Integrate([](double a,void* param) { std::vector *p = static_cast*>param; return p->at(0)*a+p->at(1); } ,0,3,p); 

    Notare che è illegale associare un valore rval a un riferimento non const, quindi non è ansible legalmente passare {2,3} come ultimo argomento a Integrate (anche se Visual Studio lo consente), sarà necessario creare una variabile denominata .

    È meglio incapsulare la conversione di void * all’interno della tua funzione wrapper:

     double Integrate(std::function func, double a, double b) { gsl_integration_workspace * w = gsl_integration_workspace_alloc (1000); gsl_function F; F.function = [](double a, void *param) { return (*static_cast *>(param))(a); }; F.params = (void*)&func; double error,result; gsl_integration_qag (&F, a, b, 0, 1e-7, 1000,GSL_INTEG_GAUSS61,w, &result, &error); gsl_integration_workspace_free (w); return result; } void Another_function() { //... std::vector params = {2, 3}; Integrate([params](double a) { return (params[0]*a+params[1]; }, 0, 3); } 

    Esiste una certa quantità di riferimento indiretto in eccesso (tramite std::function ), ma il predittore di ramo della CPU sarà in grado di funzionare bene poiché l’indirezione sarà sempre allo stesso lambda.

    Se hai bisogno di integrare una funzione lambda con cattura (in questo caso non c’è conversione al puntatore raw), e se non vuoi avere le penalizzazioni prestazionali associate a std :: function (come sottolineato da sellibitze – vedi std :: function vs template ), puoi usare il seguente wrapper

      template< typename F > class gsl_function_pp : public gsl_function { public: gsl_function_pp(const F& func) : _func(func) { function = &gsl_function_pp::invoke; params=this; } private: const F& _func; static double invoke(double x, void *params) { return static_cast(params)->_func(x); } }; 

    Ecco un codice di prova che mostra come usarlo

      double a = 1; auto ptr = [=](double x)->double{return a*x;}; gsl_function_pp Fp(ptr); gsl_function *F = static_cast(&Fp); 

    Se vuoi davvero usare std :: function, allora puoi usare questa versione del wrapper

     class gsl_function_pp : public gsl_function { public: gsl_function_pp(std::function const& func) : _func(func){ function=&gsl_function_pp::invoke; params=this; } private: std::function _func; static double invoke(double x, void *params) { return static_cast(params)->_func(x); } }; 

    Il codice di test in questo caso è ancora più semplice

     double a = 1; gsl_function_pp Fp([=](double x)->double{return a*x;}); gsl_function *F = static_cast(&Fp); 

    La cosa bella di questi wrapper è che possono essere usati anche per integrare le funzioni dei membri della class.