Функции с указателями в 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*)¶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);
};
он компилируется и отлично работает. Как мне решить эту проблему?
5 ответов
использование void * типично для интерфейсов обратного вызова C для передачи некоторого" состояния " функции. Однако std::function не нуждается в этом, потому что std:: function поддерживает "функции с состоянием". Итак, ты!--4-->может сделайте что-нибудь вроде этого:
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:: решение.
похоже, что библиотека 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;
//...
}
A std::function<>
невозможно преобразовать в указатель функции. std::function<>
являются объектами функций, которые потенциально могут содержать состояние, в то время как обычные функции не имеют состояния (вид, вы могли бы потенциально иметь static
переменные, но это разные вещи).
С другой стороны, лямбда can преобразуется в указатель функции, поэтому вы можете потенциально изменить сигнатуру своей функции, чтобы взять указатель функции напрямую и лямбду преобразуется:
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);
обратите внимание, что связывать правосторонним значением к ссылке non-const, поэтому вы не можете законно пройти {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
), но предсказатель ветви CPU сможет работать хорошо, так как косвенность всегда будет одной и той же лямбда.
Если вам нужно интегрировать лямбда-функцию с capture (в этом случае нет преобразования в необработанный указатель), и если вы не хотите иметь штрафы за производительность, связанные с функцией std:: (как указано 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::, то вы можете использовать эту версию обертка
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);
хорошая вещь об этой оболочке заключается в том, что они также могут использоваться для интеграции функций-членов класса.