C указатели на функции с C++11 ламбда

Така че се опитвам да напиша интеграционна функция, която да се използва с 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);
   };

Компилира се и работи добре. Как трябва да реша това?


person UldisK    schedule 08.11.2012    source източник
comment
Имате ли нужда от гъвкавостта на std::function? Можете ли да промените първия параметър на Integerate да бъде указател на функция? Защото няма начин да можете да използвате std::function като указател на функция, освен ако не попаднете в някакъв много грозен бизнес с глобални променливи. Имайте предвид, че можете да съхранявате ламбда в указател на функция, стига да не улавя, което в примера, който показахте, не го прави.   -  person Benjamin Lindley    schedule 08.11.2012
comment
Ето отговора ви в последното изречение на първия коментар.   -  person Lightness Races in Orbit    schedule 08.11.2012
comment
Всъщност няма нужда от глобални, тъй като C интерфейсът включва void* параметър, през който трябва да се премине.   -  person aschepler    schedule 08.11.2012
comment
Първоначално всъщност нямах нужда от него, но като видях отговорите на sellibitze (и ecatmur), открих, че с помощта на std::function мога да премахна нещата void * сега.   -  person UldisK    schedule 08.11.2012
comment
Отговорът дойде доста късно, но тази обвивка може да ви помогне много при този вид проблеми. Може също да се използва за интегриране на членски функции, за свързване на допълнителни параметри....   -  person Vivian Miranda    schedule 31.08.2013


Отговори (5)


Използването на 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 ще извика вашата ламбда. Вашата ламбда ще извика 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.

person sellibitze    schedule 08.11.2012
comment
Много ми харесва последното, много хубаво решение! - person wal-o-mat; 19.08.2013
comment
какви са :::? държачи на място? - person pyCthon; 24.08.2013
comment
@pyCthon: Да, тук трябва да е място за повече неща. C++ вече е претоварен ... с други значения. Това е многоточие и оператор за разширяване на пакет. ;) - person sellibitze; 27.08.2013

Изглежда, че библиотеката gsl изисква указател на функция. Ламбда, която не улавя, може да бъде преобразувана в указател на функция. Всяка ламбда може да бъде преобразувана в std::function. Но std::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;
    //...
 }
person aschepler    schedule 08.11.2012

Добре, колко странно, получавам „python 2.7.2“, когато напиша „python -V“
person David Rodríguez - dribeas    schedule 08.11.2012

Най-добре е да капсулирате преобразуването 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), но предсказателят на клона на процесора ще може да работи добре, тъй като индиректността винаги ще бъде към една и съща ламбда.

person ecatmur    schedule 08.11.2012

Ако трябва да интегрирате ламбда функция с улавяне (в този случай няма преобразуване към необработен указател) и ако не искате да имате наказания за производителност, свързани със std::function ( както е посочено от sellibitze - вижте std::function срещу шаблон), можете да използвате следната обвивка

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

Хубавото на тези обвивки е, че те могат да се използват и за интегриране на функции на членове на класа.

person Vivian Miranda    schedule 23.08.2013