Указатели на функции 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);
};

Компилируется и работает нормально. Как мне решить это?

6

Решение

Использование 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 ::<> :: 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.

4

Другие решения

Похоже, библиотека 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;
//...
}
3

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 позволяет вам), вам нужно будет создать именованную переменную.

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

3

Если вам нужно интегрировать лямбда-функцию с перехватом (в этом случае нет преобразования в необработанный указатель), и если вы не хотите, чтобы снижение производительности связывалось с 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);

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

3
По вопросам рекламы [email protected]