web-dev-qa-db-ja.com

C ++ 11のパラメータとして匿名関数を渡し、実行する方法は?

私が探しているコードは次のようなものです。

bool Func1(int Arg1, C++11LambdaFunc Arg2){
    if(Arg1 > 0){
        return Arg2(Arg1);
    }
}

後でこのコードを使用します。

Func1(12, [](int D) -> bool { ... } );
33
Mohsen Sarkar

ヘッダーファイルで使用する基本バージョン:

_template<typename Lambda>
bool Func1(int Arg1, Lambda Arg2){ // or Lambda&&, which is usually better
  if(Arg1 > 0){
    return Arg2(Arg1);
  } else {
    return false; // remember, all control paths must return a value
  }
}
_

より複雑なバージョン、インターフェースを実装から分離したい場合(実行時間のコストがかかります):

_bool Func1(int Arg1, std::function<bool(int)> Arg2){
  if(Arg1 > 0){
    return Arg2(Arg1);
  } else {
    return false; // remember, all control paths must return a value
  }
}
_

_std::function_は、型消去を使用してラムダのカスタム作成ラッパーを作成し、pImplパターンを使用する非仮想インターフェイスを公開して、カスタム作成ラッパーに転送します。1

または、あまり技術的には、std::function<bool(int)>は、関数のように呼び出すことができるほぼすべてをラップできるクラスであり、intを渡すことと互換性のある1つのパラメーターを渡します。 bool

_std::function_を介した呼び出しは、virtual関数呼び出し(上記のタイプの消去によって引き起こされる)とほぼ等しい実行時コストを持ち、作成するときは、渡された関数オブジェクト(別名ファンクター)の状態をコピーする必要がありますで(これは安価である可能性があります-ステートレスラムダ、または参照によって引数をキャプチャするラムダ-または他の場合には高価です)、それを保存します(通常は無料ストアまたはヒープに費用がかかります)呼び出しの時点で「インライン化」できます(つまり、関数呼び出しよりもコストが低いだけでなく、コンパイラーは関数呼び出しを最適化して境界を返すことさえできます!)

最初の例の派手なバージョンで、いくつかのコーナーケースも少しうまく処理します:(ヘッダーファイル内、または使用されているのと同じ翻訳単位でも実装する必要があります)

_template<typename Lambda>
bool Func1(int Arg1, Lambda&& Arg2){
  if(Arg1 > 0){
    return std::forward<Lambda>(Arg2)(Arg1);
  } else {
    return false; // remember, all control paths must return a value
  }
}
_

「完全転送」として知られる手法を使用します。一部のファンクターの場合、これは#1とは少し異なる動作を生成します(通常はより正確な動作)。

改善のほとんどは、引数リストで_&&_を使用することによってもたらされます。これは、ファンクターへの参照が(コピーの代わりに)渡されることを意味し、コストを節約し、constまたはnonconst渡されるファンクター。

std::forward<Lambda>(...)の変更は、誰かがメソッド(operator()を含む)がthisポインターの右辺値/左辺値ステータスをオーバーライドできる比較的新しいC++機能を使用した場合にのみ動作の変更を引き起こします。理論的には、これは有用かもしれませんが、実際にthisの右辺値ステータスに基づいてオーバーライドするファンクターの数は_0_です。深刻なライブラリコード(tm)を書いているとき、私はこのわざわざに行きますが、そうでないことはめったにありません。

考慮すべきことがもう1つあります。 boolを返す関数、またはvoidを返す関数のいずれかを使用し、関数がvoidを返す場合、trueを返したものとして処理する必要があるとします。例として、コレクションを反復処理するときに呼び出される関数を使用しており、オプションで早期停止をサポートしたい場合があります。この関数は、途中で停止したい場合はfalseを返し、そうでない場合はtrueまたはvoidを返します。

または、より一般的なケースでは、関数のオーバーライドが複数あり、そのうちの1つが関数を受け取り、他の関数が同じ場所で他のタイプを受け取ります。

これは可能です。私がここに入るまでです(スマートアダプターを使用するか、SFINAEテクニックを使用して)。ただし、2つの異なる名前付き関数を作成することをお勧めします。これは、必要な手法が非常に重いためです。


1 技術的には_std::function_は、その動作が実装ではなく標準によって記述されているため、魔法の妖精の塵を使用してそれを行うことができます。私がやり取りした_std::function_実装の動作を近似する単純な実装を説明しています。

最初の解決策:

Func1()関数を関数templateにすることができます:

_template<typename T>
bool Func1(int Arg1, T&& Arg2){
    if(Arg1 > 0){
        return Arg2(Arg1);
    }

    return false; // <== DO NOT FORGET A return STATEMENT IN A VALUE-RETURNING
                  //     FUNCTION, OR YOU WILL GET UNDEFINED BEHAVIOR IF FLOWING
                  //     OFF THE END OF THE FUNCTION WITHOUT RETURNING ANYTHING
}
_

その後、必要に応じて呼び出すことができます。

_int main()
{
    Func1(12, [](int D) -> bool { return D < 0; } );
}
_

第二の解決策:

テンプレートを使用したくない場合、代替手段(実行時のオーバーヘッドをもたらす)は _std::function_ を使用することです。

_#include <functional>

bool Func1(int Arg1, std::function<bool(int)> Arg2){
    if(Arg1 > 0){
        return Arg2(Arg1);
    }

    return false;
}
_

繰り返しになりますが、これによりFunc1()を好きな方法で呼び出すことができます:

_int main()
{
    Func1(12, [](int D) -> bool { return D < 0; } );
}
_
19
Andy Prowl

好みがより伝統的な人のために、キャプチャしていないラムダは関数ポインタに変換できることに注意してください。したがって、上記の関数を次のように書くことができます。

bool Func1(int Arg1, bool (*Arg2)(int)) { ... }

そして、従来の関数and lambdasの両方で正しく動作します。

11
Andy Ross