2012-11-08 16 views
6

Quindi sto provando a scrivere una funzione di integrazione da utilizzare con lambda C++ 11. Il codice simile a questa:C point pointer con C++ 11 lambda

double Integrate(std::function<double(double,void*)> func, double a,double b,std::vector<double> & 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<double> *)params)->at(0)*a+((vector<double> *)params)->at(1); 
    } 
    ,0,3,{2,3}); 
} 

Cercando di compilare questo, compilatore dice:

error: cannot convert ‘std::function<double(double, void*)>’ to ‘double (*)(double, void*)’ in assignment 

sulla linea

F.function =func; 

Ma se scrivo:

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

Compila e funziona bene. Come dovrei risolvere questo?

+6

Avete bisogno della versatilità di 'std :: function'? Potresti cambiare il primo parametro di "Integerate" come puntatore a funzione? Perché non c'è modo che tu possa usare una 'std :: function' come puntatore a funzione, a meno che tu non entri in qualche business delle variabili globali molto brutto. Si noti che è possibile memorizzare un lambda in un puntatore a funzione, purché non catturi, il che, nell'esempio che hai mostrato, non lo fa. –

+0

C'è la tua risposta, nella frase finale del primo commento. –

+0

In realtà non c'è bisogno di globals, poiché l'interfaccia C include un parametro 'void *' da passare. – aschepler

risposta

4

Utilizzando un void * è tipico di interfacce di callback C per passare un po 'di "stato" per la funzione. Tuttavia, std :: function non ha bisogno di questo perché std :: function supporta "stateful functions". Quindi, si potrebbe fare qualcosa di simile:

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

e memorizzare un riferimento al parametro vettore come parte del funtore che sarà incapsulato nell'oggetto std :: funzione o fare qualcosa di simile:

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 la funzione std :: <> :: 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 eliminare un paio di livelli, in particolare std :: function. Ecco un altro approccio con un modello di funzione:

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

Credo che preferirei questo rispetto alla soluzione std :: function.

+0

Mi piace molto l'ultima soluzione, molto bella! –

+0

quali sono i ':::' 's? segnaposto? – pyCthon

+1

@pyCthon: Sì, dovrebbe essere un segnaposto "more stuff here". Il C++ ha già sovraccaricato "..." con altri significati. È un ellissi e un operatore di espansione del pacchetto. ;) – sellibitze

3

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 std::function. Ma un std::function non può essere convertito in un puntatore a funzione.

Si potrebbe provare:

struct functor_and_params { 
    std::function<double(double, void*)> f; 
    void* params; 
    static double invoke(double x, void* ptr) { 
     functor_and_params& f_and_p = *reinterpret_cast<functor_and_params*>(ptr); 
     return f_and_p.f(x, f_and_p.params); 
    } 
}; 

double Integrate(std::function<double(double,void*)> func, 
       double a,double b,std::vector<double> & params) { 
    functor_and_params f_and_p{ func, &params }; 
    gsl_function F; 
    F.function = &functor_and_params::invoke; 
    F.params = &f_and_p; 
    //... 
} 
3

A std::function<> non può essere convertito in un puntatore di funzione. std::function<> sono oggetti funzione potenzialmente in grado di mantenere lo stato mentre le funzioni regolari sono stateless (tipo, potresti potenzialmente avere variabili static, ma questa è una cosa diversa).

D'altra parte, apolidi lambda può essere convertito in un puntatore a funzione, così si potrebbe potenzialmente cambiare la firma della funzione di prendere direttamente un puntatore a funzione e il lambda sarà convertito:

double Integrate(double(*func)(double,void*), double a, double b, 
       std::vector<double> & params) // !!! 

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

Nota che è illegale associare un rvalue a un riferimento non-const, quindi non può legalmente passare {2,3} come ultimo argomento a Integrate (anche se Visual Studio di consente), è necessario creare un nome variabile.

3

E 'meglio per incapsulare la conversione void * all'interno della vostra funzione wrapper:

double Integrate(std::function<double(double)> 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<std::function<double(double)> *>(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<double> params = {2, 3}; 
    Integrate([params](double a) { return (params[0]*a+params[1]; }, 0, 3); 
} 

C'è una certa quantità di eccesso di indirezione qui (attraverso std::function), ma predizione delle diramazioni della CPU sarà in grado di svolgere bene il riferimento indiretto sarà sempre allo stesso lambda.

3

Se è necessario integrare una funzione lambda con cattura (in questo caso non c'è conversione al puntatore raw), e se non si desidera avere le penalizzazioni prestazionali associate a std :: function (come sottolineato da sellibitze - vedi std::function vs template), è possibile utilizzare il seguente involucro

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<gsl_function_pp*>(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<decltype(ptr)> Fp(ptr); 
gsl_function *F = static_cast<gsl_function*>(&Fp); 

Se davvero si vuole utilizzare la funzione std ::, allora si puoi usare questa versione del wrapper

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

La cosa bella di quelli involucro è che possono essere utilizzati anche per integrare le funzioni membro della classe.