Поэтому я пытаюсь написать функцию интеграции для использования с лямбдами с ++ 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*)¶ms;
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);
};
Компилируется и работает нормально. Как мне решить это?
Использование 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.
Похоже, библиотека 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, ¶ms };
gsl_function F;
F.function = &functor_and_params::invoke;
F.params = &f_and_p;
//...
}
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 позволяет вам), вам нужно будет создать именованную переменную.
Лучше всего инкапсулировать 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
), но предсказатель ветвления ЦП будет в состоянии работать хорошо, поскольку косвенное обращение всегда будет к той же самой лямбде.
Если вам нужно интегрировать лямбда-функцию с перехватом (в этом случае нет преобразования в необработанный указатель), и если вы не хотите, чтобы снижение производительности связывалось с 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);
Приятной особенностью этих оболочек является то, что они также могут использоваться для интеграции функций-членов класса.