6

所以我正在尝试编写一个与 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});
}

试图编译这个,编译器说:

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

关于线

F.function =func;

但如果我写:

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

它编译并且工作正常。我应该如何解决这个问题?

4

5 回答 5

4

使用 void* 是典型的 C 回调接口将一些“状态”传递给函数。但是, std::function 不需要这个,因为 std::function 支持“有状态函数”。所以,你可以这样做:

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

并将对参数向量的引用存储为函子的一部分,该函子将封装在 std::function 对象中,或者执行以下操作:

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

然而,这个解决方案将使用相当多的间接。GSL 会调用你的 lambda。您的 lambda 将调用 std::function<>::operator() ,而后者又会调用某种用于类型擦除的虚函数,而该虚函数又会调用实际的计算。

所以,如果你关心性能,你可以去掉那里的几个层,特别是 std::function。这是另一种使用函数模板的方法:

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

我想我更喜欢这个而不是 std::function 解决方案。

于 2012-11-08T13:02:49.980 回答
3

如果您需要将 lambda 函数与捕获集成(在这种情况下,没有转换为原始指针),并且如果您不希望有与 std::function 相关的性能损失(正如 sellibitze 所指出的那样 - 请参阅std ::function vs template),您可以使用以下包装器

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

这是一个测试代码,展示了如何使用它

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

如果你真的想使用 std::function,那么你可以使用这个版本的包装器

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

这种情况下的测试代码就更简单了

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

这些包装器的好处是它们也可以用于集成类成员函数。

于 2013-08-23T23:14:32.987 回答
3

最好将void *转换封装在包装函数中:

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

这里有一定数量的过度间接(通过std::function),但 CPU 的分支预测器将能够很好地执行,因为间接将始终指向相同的 lambda。

于 2012-11-08T13:24:19.890 回答
3

Astd::function<>不能转换为函数指针。std::function<>是可以保持状态的函数对象,而常规函数是无状态的(有点,你可能有static变量,但那是另一回事)。

另一方面,无状态lambda可以转换为函数指针,因此您可能会更改函数的签名以直接获取函数指针,并且 lambda 将被转换:

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

请注意,将右值绑定到非常量引用是非法的,因此您不能合法地将{2,3}其作为最后一个参数传递给Integrate(即使 Visual Studio 允许),您需要创建一个命名变量。

于 2012-11-08T13:04:18.933 回答
3

看起来 gsl 库需要一个函数指针。未捕获的 lambda 可以转换为函数指针。任何 lambda 都可以转换为std::function. 但是 astd::function不能转换为函数指针。

你可以试试:

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;
    //...
 }
于 2012-11-08T13:00:18.830 回答