So i'm trying to write an Integration function to be used with c++11 lambdas. The code looks something like this:
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*)¶ms;
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});
}
Trying to compile this, compiler says:
error: cannot convert ‘std::function<double(double, void*)>’ to ‘double (*)(double, void*)’ in assignment
about line
F.function =func;
But if I write:
F.function =[](double a,void* param)
{
return ((std::vector<double> *)param)->at(0)*a+((std::vector<double> *)param)->at(1);
};
It compiles and works fine. How should I solve this?
Using a void* is typical of C callback interfaces to pass some "state" to the function. However, std::function does not need this because std::function supports "stateful functions". So, you could do something like this:
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;
:::
}
and store a reference to the parameter vector as part of the functor that will be encapsulated in the std::function object or do something like this:
void Another_function()
{
double m = 2;
double b = 3;
auto func = [&](double x){return m*x+b};
auto r1 = Integrate(func,0,3);
:::
}
However, this solution would use rather many indirections. GSL would invoke your lambda. Your lambda would invoke the std::function<>::operator() which in turn would inwoke some kind of virtual function that is used for type erasure which would in turn invoke the actual computation.
So, if you care about performance, you could get rid of a couple of layers there, specifically std::function. Here's a another approach with a function template:
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;
:::
}
I guess I would prefer this over the std::function solution.
Looks like the gsl library requires a function pointer. A lambda which does not capture can be converted to a function pointer. Any lambda can be converted to a std::function
. But a std::function
cannot be converted to a function pointer.
You could try:
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, ¶ms };
gsl_function F;
F.function = &functor_and_params::invoke;
F.params = &f_and_p;
//...
}
A std::function<>
cannot be converted to a function pointer. std::function<>
are function objects that can potentially hold state while regular functions are stateless (kind of, you could potentially have static
variables, but that is a different thing).
On the other hand, stateless lambdas can be converted to a function pointer, so you could potentially change the signature of your function to take a function pointer directly and the lambda will be converted:
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);
Note that it is illegal to bind an rvalue to a non-const reference, so you cannot legally pass {2,3}
as the last argument to Integrate
(even if Visual Studio allows you to), you will need to create a named variable.
It's best to encapsulate the void *
conversion inside your wrapper function:
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);
}
There is a certain amount of excess indirection here (through std::function
) but the CPU's branch predictor will be able to perform well as the indirection will always be to the same lambda.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With