C function pointers with C++11 lambdas

2019-01-07 22:06发布

问题:

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?

回答1:

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.



回答2:

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;
    //...
 }


回答3:

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.



回答4:

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.



回答5:

If you need to integrate a lambda function with capture (in this case there is no conversion to raw pointer), and if you don't want to have the performance penalties associated with std::function ( as pointed out by sellibitze - see std::function vs template), you can use the following 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<gsl_function_pp*>(params)->_func(x);
 }
 };

Here is a test code that shows how to use it

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

If you really want to use std::function, then you can use this version of the 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);
   }
};

The test code in this case is even simpler

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

The nice thing about those wrapper is that they can also be used to integrate class member functions.



标签: c++ c++11 gsl