2012-11-08 14 views
6

私は、C++ 11ラムダで使用する統合関数を記述しようとしています。C++ 11ラムダC関数ポインタ

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

'std :: function'の多用性が必要ですか? 'Integerate'の最初のパラメータを関数ポインタに変更できますか?なぜなら、非常に醜いグローバル変数ビジネスに参入しない限り、関数ポインタとして 'std :: function'を使用する方法はないからです。ラムダをキャプチャしない限り、ラムダを関数ポインタに格納することができますが、表示した例ではそうではありません。 –

+0

あなたの答えは、最初のコメントの最後の文です。 –

+0

実際には、C言語インタフェースには 'void *'パラメータが渡されるため、グローバルは必要ありません。 – aschepler

答えて

4

を無効を使用して*関数にいくつかの「状態」を渡すために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 ::関数オブジェクトにカプセル化されます数子の一環として、パラメータベクトルへの参照を格納したり、このような何かを::だから、あなたはこのような何かを行うことができ

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ソリューションよりもこの方法が好きでしょう。

+0

私は最後の1つ、とても良い解決策が本当に好きです! –

+0

':::'は何ですか?プレースホルダー? – pyCthon

+1

@pyCthon:はい、それは "ここにあるもの"のプレースホルダーであるはずです。 C++はすでに他の意味で "..."をオーバーロードしていました。省略記号とパック展開演算子です。 ;) – sellibitze

3

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

あなたが合法的にIntegrateの最後の引数として{2,3}を渡すことはできませんので、非const参照に右辺値をバインドすることは違法であることに注意してください(Visual Studioがあなたがすることができたとしても)、という名前を作成する必要があります変数。

3

それはあなたのラッパー関数内void *変換をカプセル化するのが最善です:間接としてだけでなく実行することができます(ここでstd::functionを介して)過剰間接の一定量があり

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

が、CPUの分岐予測器常に同じラムダになります。

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

は、あなたが、あなたが本当にのstd ::機能を使用したい場合は、それを

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

を使用する方法を示してテストコードで次のラッパーを使用することができますこのバージョンのラッパーを使用できます

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

それらのラッパーのいいところは、彼らは、クラスのメンバ関数を統合するためにも使用することができるということです。