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


210

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

次の例を考えてみましょう

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つですが、ラムダをaとして処理できないconstexprため、関数ポインタとして渡すことができないというのが私が理解していることだと思います。私xもconstを作ってみましたが、それは役に立たないようです。


34
ラムダは、何もキャプチャしない場合にのみ、関数ポインタに減衰できます。
Jarod42 2015


後世のために、上記のリンクされたブログ投稿は現在devblogs.microsoft.com/oldnewthing/20150220-00/?p=44623
warrenm

回答:


205

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

ラムダ式の閉じ方の種類なしラムダキャプチャでは、公共の非仮想非明示的なの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重いです、それはコストレストレードオフではありませんので、。


2
補足:Cスタッフが使用する一般的な解決策の1つは、a void*を唯一のパラメーターとして渡すことです。通常は「ユーザーポインタ」と呼ばれます。また、比較的軽量ですmallocが、スペースが必要になる傾向があります。
モニカの訴訟に資金を提供

94

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

4
@TC それが機能する理由の詳細については、この質問を参照してください
Shafik Yaghmour 2015

一般的に、コンパイル時にキャプチャデータがわかっている場合は、それをタイプデータに変換して、キャプチャなしのラムダに戻すことができます。この回答を参照してください。別の質問に書いただけです(@に感謝) 5gon12ederの答えはこちら)。
dan-man

オブジェクトは、ポインタ関数よりも長い寿命を持つべきではありませんか?に使いたいですglutReshapeFunc
ar2015

この提案はお勧めしません。魔法のように機能する傾向があるため、新しいエラーが発生します。そして、それらのエラーに伴う慣行。std :: functionを使用する場合は、std :: functionを使用できるあらゆる種類の方法を確認する必要があります。いくつかの方法は、おそらくあなたが望まないものだからです。
TheNegative

1
これは質問の答えにはなりません。std::functionまたはラムダを使用できるとしたら、なぜ使用しないのですか?少なくとも、より読みやすい構文です。通常、関数ポインターを使用してCライブラリー(実際には任意の外部ライブラリー)と対話する必要があり、std :: functionまたはlambdaを受け入れるように変更することはできません。
Hi-Angel

40

ラムダ式は、キャプチャされたものでも、関数ポインター(メンバー関数へのポインター)として処理できます。

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

クリエイティブになったら、これを使えます!std :: 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で動作します

アップデート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;
}

わおそれは驚きだ!したがって、ラムダのクラス内部ポインタ(メンバー関数operator()へ)を使用して、ラッパークラスに格納されたラムダを呼び出すことができます。素晴らしい!! では、なぜstd :: functionが必要になるのでしょうか。そして、lambda_expression <decltype(lambda)、int、int、int>に、渡されたラムダ自体から直接これらの「int」パラメータを直接自動的に演繹するようにすることは可能ですか?
barney

2
自分のコードの短いバージョンを追加しました。これは単純なauto f = make :: function(lambda);で動作するはずです。しかし、私はあなたが私のコードが機能しない状況をたくさん見つけると確信しています。std :: functionはこれよりもずっとよく構成されており、作業しているときの手がかりになるはずです。これは、教育と個人的な使用のためのものです。
Noxxer 2017

14
このソリューションには、operator()実装を介してラムダを呼び出す必要があるため、正しく読んでいる場合は、Cスタイルの関数ポインターを使用してラムダを呼び出すことはできないと思いますか?それが、元の質問が求めていたものです。
レミールボー2017

13
ラムダは関数ポインタとして処理できると主張しましたが、実際にはそうではありませんでした。ラムダを保持する別のオブジェクトを作成しましたが、これは何もしません。元のラムダをそのまま使用することもできます。
通過者2017年

9
これは「ラムダのキャプチャを関数ポインタとして渡す」ことではありません。これは「ラムダのキャプチャを、とりわけ関数ポインタを含むオブジェクトとして渡す」ことです。違いの世界があります。
n。「代名詞」m。

15

この答えのように、ラムダのキャプチャは関数ポインタに変換できません指摘したように。

ただし、APIを1つしか受け付けない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!
}

住む

これは基本的に、発生するたびに無名関数を宣言しています fnptrます。

同じタイプのfnptr以前に書き込まれたcallable指定の呼び出し可能オブジェクトを上書きすることに注意してください。intパラメータを使用して、これをある程度改善しますN

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

N整数を強制的に宣言することは、コンパイル時に関数ポインタを上書きしないようにクライアントを覚えておくための洗練された方法です。
fiorentinoing

2

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

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

Curlを例として使用する(curlデバッグ情報

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

3
そのラムダにはキャプチャがありません。OPの問題はキャプチャであり、関数のポインタ型を推測する必要はありません(これにより+トリックが得られます)。
Sneftel

2

テンプレートのアプローチはさまざまな理由で賢いですが、ラムダのライフサイクルと取得した変数を覚えておくことが重要です。ラムダポインターのいずれかの形式が使用され、ラムダが下向きの継続ではない場合、コピーする[=]ラムダのみを使用する必要があります。つまり、その場合でも、スタック上の変数へのポインタのキャプチャは、キャプチャされたポインタの有効期間(スタックの巻き戻し)がラムダの有効期間よりも短い場合、安全ではありません。

ラムダをポインタとしてキャプチャするためのより簡単なソリューションは次のとおりです。

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

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

後で覚えておいdelete pLamdbaてください。ラムダメモリがリークしないようにしてください。ここで理解する秘密は、ラムダがラムダをキャプチャできること(そのしくみを自問自答すること)、およびstd::functionラムダ実装を一般的に機能させるために、ラムダ(およびキャプチャされた)データのサイズへのアクセスを提供するための十分な内部情報を含める必要があることです(これが、delete[キャプチャされた型のデストラクタを実行する] が機能する理由です。


new-std :: functionが煩わしい理由は、すでにラムダをヒープに格納していて、deleteの呼び出しを覚えておく必要がないようにするためです。
クリスドッド

0

直接的な回答ではありませんが、「ファンクター」テンプレートパターンを使用してラムダ型の詳細を隠し、コードを簡潔に保つためのわずかなバリエーションがあります。

決定クラスをどのように使用するかがわからなかったので、それを使用する関数でクラスを拡張する必要がありました。ここで完全な例を参照してください:https : //godbolt.org/z/jtByqE

クラスの基本的な形式は次のようになります。

template <typename Functor>
class Decide
{
public:
    Decide(Functor dec) : _dec{dec} {}
private:
    Functor _dec;
};

次のように使用されるクラス型の一部として関数の型を渡す場所:

auto decide_fc = [](int x){ return x > 3; };
Decide<decltype(decide_fc)> greaterThanThree{decide_fc};

繰り返しますが、なぜxラムダに渡すパラメータを持つ方が(私にとって)意味があるのか​​、なぜキャプチャするのかわかりませんでした。

int result = _dec(5); // or whatever value

完全な例については、リンクを参照してください


-2

他の人が言及したように、関数ポインターの代わりに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);
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.