43

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

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

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

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

3 に答える 3

60

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

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::functionpImpl型消去を使用して、ラムダの周りにカスタム作成されたラッパーを作成し、パターンを使用してカスタム作成されたラッパーに転送する非仮想インターフェイスを公開します。1

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

を介した呼び出しstd::functionのランタイム コストは、virtual関数呼び出しとほぼ同じであり (上記の型の消去が原因で発生します)、それを作成するときは、渡された関数オブジェクト (別名ファンクター) の状態をコピーする必要があります (これは安価な場合があります)。 -- ステートレス ラムダ、または参照によって引数をキャプチャするラムダ -- または、他の場合にはコストがかかる) を格納し (通常は、コストがかかるフリー ストアまたはヒープに)、純粋なテンプレート バージョンは で「インライン化」できます。呼び出しのポイント (つまり、関数呼び出しよりもコストがかからないだけでなく、コンパイラは関数呼び出しを最適化し、境界を返すことさえできます!)

のすべての実行時コストなしでインターフェイス/実装を分割したい場合はstd::function、独自の function_ref をロールバックできます ( では、ボイラープレートが削減されるため)。

template<class Sig>
struct function_ref;

template<class R, class...Args>
struct function_ref<R(Args...)> {
  R operator()(Args...args) const {
    return pf(state, std::forward<Args>(args)...);
  }
  function_ref()=default;
  function_ref(function_ref const&)=default;
  function_ref& operator=(function_ref const&)=default;
  explicit operator bool()const{ return pf!=nullptr; }

  // this overload reduces indirection by 1 step
  // and allows function_ref<Sig> to resolve overloads
  // on an overload set sometimes.
  function_ref( R(*f)(Args...) ):
    pf([](State const& state, Args&&...args)->R{
      return reinterpret_cast<R(*)(Args...)>(state.pfunstate)(std::forward<Args>(args)...);
    })
  {
    state.pfunstate = reinterpret_cast<void(*)()>(f);
  }

  // this grabs anything callable (that isn't this own type)
  // and stores a pointer to it to call later.
  template<class F>
  requires (
    std::is_convertible_v<
      std::invoke_result_t< std::remove_reference_t<F>, Args... >, R
    >
    && !std::is_same_v< std::decay_t<F>, function_ref >
  )
  function_ref( F&& f ):
    pf([](State const& state, Args&&...args)->R{
      return (*(std::remove_reference_t<F>*)state.pstate)(std::forward<Args>(args)...);
    })
  {
    state.pstate = std::addressof(f);
  }
private:
  union State {
    void* pstate = nullptr;
    void(*pfunstate)();
  };
  State state;
  R(*pf)(State const&, Args&&...) = nullptr;
};
// a deduction guide permitting function_ref{foo} to work
// if foo is a non-overloaded function name.
template<class R, class...Args>
function_ref( R(*)(Args...) )->function_ref<R(Args...)>;

実例

std::functionこれにより、所有権のセマンティクスを削除し、型消去呼び出しを行うだけで、割り当てを行う必要がなくなります。

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

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または非constファンクターの両方を渡すことができます。

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

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

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

これは可能です。ここで説明する限りでは (スマート アダプターを使用するか、SFINAE 手法を使用します)。ただし、2 つの異なる名前付き関数を作成するだけの方がよいでしょう。これは、必要な手法が非常に重いためです。


1技術的std::functionには、魔法の妖精の粉を使用して、その動作が実装ではなく標準で記述されているため、それを実行できます。std::function私が対話した実装の動作に近い単純な実装について説明しています。

于 2013-04-19T18:32:06.337 に答える
24

最初の解決策:

Func1()関数を関数テンプレートにすることができます:

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

2番目の解決策:

テンプレートを使用したくない場合、別の方法 (ランタイム オーバーヘッドが発生します) は次を使用します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; } );
}
于 2013-04-19T18:32:02.200 に答える
13

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

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

また、従来の関数とラムダの両方で正しく機能します。

于 2013-04-19T18:36:29.797 に答える