web-dev-qa-db-ja.com

関数ポインターとしてキャップチャラムダを渡る

関数ポインタとしてラムダ関数を渡すことは可能ですか?もしそうなら、私はコンパイルエラーを得ているので、私は何かを間違ってやっているに違いない。

次の例を見てください

using DecisionFn = bool(*)();

class Decide
{
public:
    Decide(DecisionFn dec) : _dec{dec} {}
private:
    DecisionFn _dec;
};

int main()
{
    int x = 5;
    Decide greaterThanThree{ [x](){ return x > 3; } };
    return 0;
}

私が これをコンパイルしようとすると 、次のようなコンパイルエラーが発生します。

In function 'int main()':
17:31: error: the value of 'x' is not usable in a constant expression
16:9:  note: 'int x' is not const
17:53: error: no matching function for call to 'Decide::Decide(<brace-enclosed initializer list>)'
17:53: note: candidates are:
9:5:   note: Decide::Decide(DecisionFn)
9:5:   note: no known conversion for argument 1 from 'main()::<lambda()>' to 'DecisionFn {aka bool (*)()}'
6:7:   note: constexpr Decide::Decide(const Decide&)
6:7:   note: no known conversion for argument 1 from 'main()::<lambda()>' to 'const Decide&'
6:7:   note: constexpr Decide::Decide(Decide&&)
6:7:   note: no known conversion for argument 1 from 'main()::<lambda()>' to 'Decide&&'

これはダイジェストするためのエラーメッセージの1つですが、ここから出てくるのは、ラムダをconstexprとして扱うことができないので関数ポインタとして渡すことができないということです。私はxをconstにしてみましたが、それは助けにはならないようです。

169
CoryKramer

ラムダは、キャプチャしない場合にのみ関数ポインタに変換できます。 ドラフトC++ 11標準から section 5.1.2 [expr.prim.lambda] say( 強調) ):

ラムダ式のクロージャ型ラムダキャプチャなしは、次のものと同じパラメータと戻り型を持つ、公開された非仮想的な非明示的なconst 関数へのポインタへの変換関数を持ちます。クロージャ型の関数呼び出し演算子この変換関数によって返される値は、呼び出されたときにクロージャ型の関数呼び出し演算子を呼び出すのと同じ効果を持つ関数のアドレスです。

Cppreferenceは Lambda関数 のセクションでこれもカバーしています。

そのため、以下の代替方法がうまくいきます。

typedef bool(*DecisionFn)(int);

Decide greaterThanThree{ []( int x ){ return x > 3; } };

これもそうでしょう。

typedef bool(*DecisionFn)();

Decide greaterThanThree{ [](){ return true ; } };

5gon12eder が指摘するように、 std::function を使用することもできますが、 std::functionは重い であることに注意してください。したがって、これはコストを削減するトレードオフではありません。

173
Shafik Yaghmour

Shaf​​ik Yaghmourの答え は、ラムダがキャプチャされていると関数ポインタとして渡せない理由を正しく説明しています。この問題に対する2つの簡単な修正方法を示します。

  1. 生の関数ポインタの代わりにstd::functionを使用してください。

    これはとてもきれいな解決策です。ただし、型消去のための追加のオーバーヘッド(おそらく仮想関数呼び出し)が含まれることに注意してください。

    #include <functional>
    #include <utility>
    
    struct Decide
    {
      using DecisionFn = std::function<bool()>;
      Decide(DecisionFn dec) : dec_ {std::move(dec)} {}
      DecisionFn dec_;
    };
    
    int
    main()
    {
      int x = 5;
      Decide greaterThanThree { [x](){ return x > 3; } };
    }
    
  2. 何も捕らえないラムダ式を使う

    あなたの述語は実際にはただのブール定数なので、以下はすぐに現在の問題を回避するでしょう。その理由とその仕組みについては、 この回答 を参照してください。

    // Your 'Decide' class as in your post.
    
    int
    main()
    {
      int x = 5;
      Decide greaterThanThree {
        (x > 3) ? [](){ return true; } : [](){ return false; }
      };
    }
    
82
5gon12eder

私はこれが少し古いことを知っています。

しかし、私は加えたいと思いました:

ラムダ式(キャプチャされたものであっても)は関数ポインタとして扱うことができます!

Lambda式は単純な関数ではないため、注意が必要です。実際にはoperator()を持つオブジェクトです。

あなたが創造的であるとき、あなたはこれを使うことができます! std :: functionのスタイルの "function"クラスを考えてください。オブジェクトを保存すれば!

関数ポインタを使うこともできます。

関数ポインタを使用するには、次のものを使用できます。

int first = 5;
auto lambda = [=](int x, int z) {
    return x + z + first;
};
int(decltype(lambda)::*ptr)(int, int)const = &decltype(lambda)::operator();
std::cout << "test = " << (lambda.*ptr)(2, 3) << std::endl;

"std :: function"のように動作し始めることができるクラスを構築するために、私はただ短い例をするでしょう。最初に、オブジェクトと関数のポインタを格納できるクラス/構造体が必要です。それを実行するには、operator()も必要です。

// OT => Object Type
// RT => Return Type
// A ... => Arguments
template<typename OT, typename RT, typename ... A>
struct lambda_expression {
    OT _object;
    RT(OT::*_function)(A...)const;

    lambda_expression(const OT & object)
        : _object(object), _function(&decltype(_object)::operator()) {}

    RT operator() (A ... args) const {
        return (_object.*_function)(args...);
    }
};

これで、オリジナルのものを使っているのと同じように、キャプチャされた、キャプチャされていないラムダを実行することができます。

auto capture_lambda() {
    int first = 5;
    auto lambda = [=](int x, int z) {
        return x + z + first;
    };
    return lambda_expression<decltype(lambda), int, int, int>(lambda);
}

auto noncapture_lambda() {
    auto lambda = [](int x, int z) {
        return x + z;
    };
    return lambda_expression<decltype(lambda), int, int, int>(lambda);
}

void refcapture_lambda() {
    int test;
    auto lambda = [&](int x, int z) {
        test = x + z;
    };
    lambda_expression<decltype(lambda), void, int, int>f(lambda);
    f(2, 3);

    std::cout << "test value = " << test << std::endl;
}

int main(int argc, char **argv) {
    auto f_capture = capture_lambda();
    auto f_noncapture = noncapture_lambda();

    std::cout << "main test = " << f_capture(2, 3) << std::endl;
    std::cout << "main test = " << f_noncapture(2, 3) << std::endl;

    refcapture_lambda();

    system("PAUSE");
    return 0;
}

このコードはVS2015で動作します。

あいさつ

編集:針テンプレートFPを削除、関数ポインタパラメータを削除、lambda_expressionに名前変更

更新04.07.17:

template <typename CT, typename ... A> struct function
: public function<decltype(&CT::operator())(A...)> {};

template <typename C> struct function<C> {
private:
    C mObject;

public:
    function(const C & obj)
        : mObject(obj) {}

    template<typename... Args> typename 
    std::result_of<C(Args...)>::type operator()(Args... a) {
        return this->mObject.operator()(a...);
    }

    template<typename... Args> typename 
    std::result_of<const C(Args...)>::type operator()(Args... a) const {
        return this->mObject.operator()(a...);
    }
};

namespace make {
    template<typename C> auto function(const C & obj) {
        return ::function<C>(obj);
    }
}

int main(int argc, char ** argv) {
   auto func = make::function([](int y, int x) { return x*y; });
   std::cout << func(2, 4) << std::endl;
   system("PAUSE");
   return 0;
}
27
Noxxer

this answer が指摘しているように、ラムダのキャプチャは関数ポインタに変換できません。

しかし、それを受け入れるAPIへの関数ポインタを提供するのは、かなり面倒です。その方法として最もよく引用されている方法は、関数を提供し、それを使って静的オブジェクトを呼び出すことです。

static Callable callable;
static bool wrapper()
{
    return callable();
}

これは面倒です。私たちはこの考えをさらに進め、wrapperを作成するプロセスを自動化し、生活をより簡単にします。

#include<type_traits>
#include<utility>

template<typename Callable>
union storage
{
    storage() {}
    std::decay_t<Callable> callable;
};

template<int, typename Callable, typename Ret, typename... Args>
auto fnptr_(Callable&& c, Ret (*)(Args...))
{
    static bool used = false;
    static storage<Callable> s;
    using type = decltype(s.callable);

    if(used)
        s.callable.~type();
    new (&s.callable) type(std::forward<Callable>(c));
    used = true;

    return [](Args... args) -> Ret {
        return Ret(s.callable(std::forward<Args>(args)...));
    };
}

template<typename Fn, int N = 0, typename Callable>
Fn* fnptr(Callable&& c)
{
    return fnptr_<N>(std::forward<Callable>(c), (Fn*)nullptr);
}

そしてそれを使う

void foo(void (*fn)())
{
    fn();   
}

int main()
{
    int i = 42;
    auto fn = fnptr<void()>([i]{std::cout << i;});
    foo(fn);  // compiles!
}

Live

これは基本的にfnptrの出現ごとに無名関数を宣言しています。

同じ型のcallableが与えられた場合、fnptrの呼び出しは以前に書かれたcallableを上書きすることに注意してください。 intパラメータNを使用して、ある程度これを修正します。

std::function<void()> func1, func2;
auto fn1 = fnptr<void(), 1>(func1);
auto fn2 = fnptr<void(), 2>(func2);  // different function
9
Passer By

C関数ポインターとしてラムダを使用するためのショートカットは次のとおりです。

"auto fun = +[](){}"

例としてCurlを使用する( curl debug info

auto callback = +[](CURL* handle, curl_infotype type, char* data, size_t size, void*){ //add code here :-) };
curl_easy_setopt(curlHande, CURLOPT_VERBOSE, 1L);
curl_easy_setopt(curlHande,CURLOPT_DEBUGFUNCTION,callback);
0
janCoffee

テンプレートアプローチはさまざまな理由で巧妙ですが、ラムダのライフサイクルとキャプチャされた変数を覚えておくことが重要です。任意の形式のラムダポインタが使用され、ラムダが下向きの連続ではない場合、コピーする[=]ラムダのみが使用されるべきです。つまり、それでも、スタック上の変数へのポインタのキャプチャは、それらのキャプチャされたポインタの有効期間(スタックのアンワインド)がラムダの有効期間より短い場合はUNSAFEです。

ポインタとしてラムダをキャプチャするためのより簡単な解決策は、次のとおりです。

auto pLamdba = new std::function<...fn-sig...>([=](...fn-sig...){...});

例:new std::function<void()>([=]() -> void {...}

あとでdelete pLamdbaを忘れずに、ラムダメモリをリークしないようにしてください。ここで実現する秘訣は、ラムダがラムダをキャプチャできること(それがどのように機能するかを自問してみてください)とstd::functionが一般的に機能するためにはラムダ(そしてキャプチャされた) (これがdeleteが動作する理由です[キャプチャされた型のデストラクタの実行])。

0
smallscript

他の人が述べたように、関数ポインタの代わりにLambda関数を代用することができます。私はF77 ODEソルバーRKSUITEへの私のC++インターフェースでこのメソッドを使っています。

//C interface to Fortran subroutine UT
extern "C"  void UT(void(*)(double*,double*,double*),double*,double*,double*,
double*,double*,double*,int*);

// C++ wrapper which calls extern "C" void UT routine
static  void   rk_ut(void(*)(double*,double*,double*),double*,double*,double*,
double*,double*,double*,int*);

//  Call of rk_ut with lambda passed instead of function pointer to derivative
//  routine
mathlib::RungeKuttaSolver::rk_ut([](double* T,double* Y,double* YP)->void{YP[0]=Y[1]; YP[1]= -Y[0];}, TWANT,T,Y,YP,YMAX,WORK,UFLAG);
0
beniekg