Указатели функций C с лямбда-выражениями C++11

Итак, я пытаюсь написать функцию интеграции, которая будет использоваться с лямбда-выражениями С++ 11. Код выглядит примерно так:

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

std::function<> нельзя преобразовать в указатель функции. std::function<> - это функциональные объекты, которые потенциально могут хранить состояние, в то время как обычные функции не имеют состояния (вроде, у вас потенциально могут быть static переменные, но это другое дело).

С другой стороны, без сохранения состояния лямбда-выражения могут быть преобразованы в указатель на функцию, поэтому вы потенциально можете изменить сигнатуру своей функции, чтобы получить указатель на функцию напрямую, и лямбда-выражение будет преобразовано:

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

Обратите внимание, что недопустимо связывать rvalue с неконстантной ссылкой, поэтому вы не можете законно передать {2,3} в качестве последнего аргумента для Integrate (даже если Visual Studio позволяет вам это сделать), вам нужно будет создать именованная переменная.

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

Хорошая вещь в этих оболочках заключается в том, что их также можно использовать для интеграции функций-членов класса.

person Vivian Miranda    schedule 23.08.2013