2012-11-08 23 views
6

Próbuję więc napisać funkcję integracji, która będzie używana z lambdami C++ 11. Kod wygląda mniej więcej tak:Wskaźniki funkcyjne z C++ 11 lambdas

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

Próba skompilować tego, kompilator mówi:

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

o linię

F.function =func; 

Ale jeśli piszę:

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

Kompiluje i działa dobrze. Jak mam to rozwiązać?

+6

Potrzebujesz wszechstronności 'std :: function'? Czy mógłbyś zmienić pierwszy parametr 'Integerate' jako wskaźnik funkcji? Ponieważ nie ma możliwości użycia funkcji 'std :: function' jako wskaźnika funkcji, chyba że dostaniesz się do jakiejś bardzo brzydkiej biznesowej zmiennej globalnej. Zauważ, że możesz przechowywać lambdę w wskaźniku funkcji, o ile nie przechwytuje, co w przykładzie, który pokazałeś, nie zawiera. –

+0

Oto twoja odpowiedź, w ostatnim zdaniu pierwszego komentarza. –

+0

W rzeczywistości nie ma potrzeby globali, ponieważ interfejs C zawiera parametr "void *", który ma zostać przekazany. – aschepler

Odpowiedz

4

Korzystanie z void * jest typowe dla interfejsów zwrotnych C przekazać jakieś „państwo” do funkcji. Jednak funkcja std :: nie potrzebuje tego, ponieważ funkcja std :: obsługuje "funkcje stanowe". Więc mógłby zrobić coś takiego:

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

i przechowywać odniesienie do wektora parametrów jako część funktora, który będzie zawarta w obiekcie std :: function lub zrobić coś takiego:

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

Jednak to rozwiązanie wykorzystywałoby raczej wiele pośredników. GSL wywołałby twoją lambdę. Twoja lambda wywoła funkcję std :: function <> :: operator(), która z kolei wywołałaby jakąś funkcję wirtualną, która jest używana do wymazywania typu, co z kolei wywoływałoby rzeczywiste obliczenia.

Tak więc, jeśli zależy Ci na wydajności, możesz pozbyć się kilku warstw, w szczególności std :: function. Oto inne podejście z szablonem funkcji:

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

Chyba wolałbym to od rozwiązania std :: function.

+0

Bardzo podoba mi się ostatnie, bardzo ładne rozwiązanie! –

+0

czym są ':::'? miejsce posiadaczy? – pyCthon

+1

@pyCthon: Tak, to powinno być miejsce "więcej rzeczy tutaj". C++ już przeciążone "..." z innymi znaczeniami. To elipsa i operator rozszerzenia paczki. ;) – sellibitze

3

Wygląda na to, że biblioteka gsl wymaga wskaźnika funkcji. Wartość lambda, która nie przechwytuje, można przekonwertować na wskaźnik funkcji. Dowolną lambdę można przekonwertować na std::function. Ale std::function nie można przekonwertować na wskaźnik funkcji.

można spróbować:

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

Nie można przekonwertować A std::function<> na wskaźnik funkcji. std::function<> są obiektami funkcyjnymi, które mogą potencjalnie utrzymywać stan, podczas gdy zwykłe funkcje są bezpaństwowcami (może to potencjalnie mieć zmienne static, ale to jest coś innego).

Z drugiej strony, bezpaństwowcem lambdas może być konwertowane do wskaźnika funkcji, więc można potencjalnie zmienić podpis swojej funkcji do podjęcia bezpośrednio wskaźnik funkcji i lambda zostaną przekształcone:

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

należy pamiętać, że jest to nielegalne związać rvalue do const odniesienia, więc nie może legalnie przekazać {2,3} jako ostatni argument Integrate (nawet jeśli Visual Studio pozwala), trzeba będzie utworzyć nazwany zmienna.

3

Najlepiej symbolizować przemianę void * wewnątrz funkcji 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); 
} 

istnieje pewna ilość tutaj (poprzez std::function) nadmiaru zadnie ale CPU oddziału prognostyk będą mogli również wykonać za zadnie zawsze będzie do tej samej wartości lambda.

3

Jeśli musisz zintegrować funkcję lambda z przechwytywaniem (w tym przypadku nie ma konwersji na surowy wskaźnik), a jeśli nie chcesz mieć kar umownych związanych z funkcją std :: (jak wskazano przez sellibitze - patrz std::function vs template), można użyć następującego opakowanie

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

Oto kod testu, który pokazuje, jak używać go

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

Jeśli naprawdę chcesz używać std :: funkcję, a następnie może korzystać z tej wersji opakowania

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

Kod test w tym przypadku jest jeszcze prostsze

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

Zaletą tych owijki jest to, że mogą one również zostać wykorzystane w celu zintegrowania funkcji składowych klasy.