Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C function pointers with C++11 lambdas

Tags:

c++

c++11

gsl

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

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?

like image 993
UldisK Avatar asked Nov 08 '12 12:11

UldisK


4 Answers

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.

like image 194
sellibitze Avatar answered Oct 08 '22 21:10

sellibitze


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, &params };
    gsl_function F;
    F.function = &functor_and_params::invoke;
    F.params = &f_and_p;
    //...
 }
like image 21
aschepler Avatar answered Oct 08 '22 19:10

aschepler


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.

like image 23
David Rodríguez - dribeas Avatar answered Oct 08 '22 21:10

David Rodríguez - dribeas


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.

like image 3
ecatmur Avatar answered Oct 08 '22 19:10

ecatmur