web-dev-qa-db-ja.com

移動をキャプチャするラムダ式からstd :: functionを作成する方法は?

移動をキャプチャするラムダ式から_std::function_を作成しようとしています。移動をキャプチャするラムダ式を問題なく作成できることに注意してください。エラーが発生するのは、それを_std::function_でラップしようとした場合のみです。

例えば:

_auto pi = std::make_unique<int>(0);

// no problems here!
auto foo = [q = std::move(pi)] {
    *q = 5;
    std::cout << *q << std::endl;
};

// All of the attempts below yield:
// "Call to implicitly-deleted copy constructor of '<lambda...."

std::function<void()> bar = foo;
std::function<void()> bar{foo};
std::function<void()> bar{std::move(foo)};
std::function<void()> bar = std::move(foo);
std::function<void()> bar{std::forward<std::function<void()>>(foo)};
std::function<void()> bar = std::forward<std::function<void()>>(foo);
_

なぜこんなふうに書きたいのかを説明します。 jQueryやJavaFXと同様に、ユーザーがon_mouse_down()on_mouse_drag()などの名前のメソッドに_std::function_ sを渡すことでマウス/キーボードイベントを処理できるUIライブラリを作成しました。 Push_undo_action()など.

明らかに、渡したい_std::function_は、移動をキャプチャするラムダ式を使用するのが理想的です。それ以外の場合は、C++ 11が使用されていたときに使用していた醜い "release/acquire-in-lambda"イディオムに頼る必要があります。標準:

_std::function<void()> baz = [q = pi.release()] {
    std::unique_ptr<int> p{q};
    *p = 5;
    std::cout << *q << std::endl;
};
_

上記のコードでは、bazを2回呼び出すとエラーになります。ただし、私のコードでは、このクロージャーが1回だけ呼び出されることが保証されています。

ところで、私の実際のコードでは、_std::unique_ptr<int>_ではなく、もっと興味深いものを渡しています。

最後に、次のバージョンのclangを使用するXcode6-Beta4を使用しています。

_Apple LLVM version 5.1 (clang-503.0.40) (based on LLVM 3.4svn)
Target: x86_64-Apple-darwin13.3.0
Thread model: posix
_
54
seertaak

template<class F> function(F f);

template <class F, class A> function(allocator_arg_t, const A& a, F f);

要件:FCopyConstructibleになります。 fは、引数タイプCallableの場合はArgTypesであり、戻りタイプRになります。 Aのコピーコンストラクタとデストラクタは例外をスローしません。

§20.9.11.2.1[func.wrap.func.con]

_operator =_はこのコンストラクタとswapの観点から定義されているため、同じ制限が適用されることに注意してください。

template<class F> function& operator=(F&& f);

効果:function(std::forward<F>(f)).swap(*this);

§20.9.11.2.1[func.wrap.func.con]

だからあなたの質問に答える:はい、移動キャプチャラムダから_std::function_を構築することは可能です(これはラムダがキャプチャする方法を指定するだけなので)not構築することは可能ですa 移動専用タイプからの_std::function_(たとえば、コピー構築できない何かを移動キャプチャする移動キャプチャラムダ)。

_std::function<?>_は、格納された呼び出し可能なオブジェクトのコピーコンストラクターを型消去する必要があるため、移動のみの型から構築することはできません。ラムダは値によって移動のみのタイプをキャプチャするため、移動のみのタイプです。だから...あなたの問題を解決することはできません。 _std::function_はラムダを保存できません。

少なくとも直接ではない。

これはC++であり、問​​題を回避するだけです。

_template<class F>
struct shared_function {
  std::shared_ptr<F> f;
  shared_function() = delete; // = default works, but I don't use it
  shared_function(F&& f_):f(std::make_shared<F>(std::move(f_))){}
  shared_function(shared_function const&)=default;
  shared_function(shared_function&&)=default;
  shared_function& operator=(shared_function const&)=default;
  shared_function& operator=(shared_function&&)=default;
  template<class...As>
  auto operator()(As&&...as) const {
    return (*f)(std::forward<As>(as)...);
  }
};
template<class F>
shared_function< std::decay_t<F> > make_shared_function( F&& f ) {
  return { std::forward<F>(f) };
}
_

以上で、問題を解決できます。

_auto pi = std::make_unique<int>(0);

auto foo = [q = std::move(pi)] {
  *q = 5;
  std::cout << *q << std::endl;
};

std::function< void() > test = make_shared_function( std::move(foo) );
test(); // prints 5
_

_shared_function_のコピーは、元の関数と同じ状態(_std::function_に変換された場合を含む)を共有するため、他の関数とは少し異なります。

Move-only fire-once関数を書くこともできます:

_template<class Sig>
struct fire_once;

template<class T>
struct emplace_as {};

template<class R, class...Args>
struct fire_once<R(Args...)> {
  // can be default ctored and moved:
  fire_once() = default;
  fire_once(fire_once&&)=default;
  fire_once& operator=(fire_once&&)=default;

  // implicitly create from a type that can be compatibly invoked
  // and isn't a fire_once itself
  template<class F,
    std::enable_if_t<!std::is_same<std::decay_t<F>, fire_once>{}, int> =0,
    std::enable_if_t<
      std::is_convertible<std::result_of_t<std::decay_t<F>&(Args...)>, R>{}
      || std::is_same<R, void>{},
      int
    > =0
  >
  fire_once( F&& f ):
    fire_once( emplace_as<std::decay_t<F>>{}, std::forward<F>(f) )
  {}
  // emplacement construct using the emplace_as tag type:
  template<class F, class...FArgs>
  fire_once( emplace_as<F>, FArgs&&...fargs ) {
    rebind<F>(std::forward<FArgs>(fargs)...);
  }
  // invoke in the case where R is not void:
  template<class R2=R,
    std::enable_if_t<!std::is_same<R2, void>{}, int> = 0
  >
  R2 operator()(Args...args)&&{
    try {
      R2 ret = invoke( ptr.get(), std::forward<Args>(args)... );
      clear();
      return ret;
    } catch(...) {
      clear();
      throw;
    }
  }
  // invoke in the case where R is void:
  template<class R2=R,
    std::enable_if_t<std::is_same<R2, void>{}, int> = 0
  >
  R2 operator()(Args...args)&&{
    try {
      invoke( ptr.get(), std::forward<Args>(args)... );
      clear();
    } catch(...) {
      clear();
      throw;
    }
  }

  // empty the fire_once:
  void clear() {
    invoke = nullptr;
    ptr.reset();
  }

  // test if it is non-empty:
  explicit operator bool()const{return (bool)ptr;}

  // change what the fire_once contains:
  template<class F, class...FArgs>
  void rebind( FArgs&&... fargs ) {
    clear();
    auto pf = std::make_unique<F>(std::forward<FArgs>(fargs)...);
    invoke = +[](void* pf, Args...args)->R {
      return (*(F*)pf)(std::forward<Args>(args)...);
    };
    ptr = {
      pf.release(),
      [](void* pf){
        delete (F*)(pf);
      }
    };
  }
private:
  // storage.  A unique pointer with deleter
  // and an invoker function pointer:
  std::unique_ptr<void, void(*)(void*)> ptr{nullptr, +[](void*){}};
  void(*invoke)(void*, Args...) = nullptr;
};
_

_emplace_as<T>_タグを介して移動できないタイプもサポートします。

実例

サイレントな破壊的な_()_は失礼に思えたため、右辺値コンテキストで_std::move_を評価する必要があることに注意してください(つまり、_()_の後)。

この実装では、SBOを使用しません。使用した場合、格納されたタイプが移動可​​能であることが要求され、ブートするのが(私にとって)より多くの作業になるためです。

これはより簡単な解決策です:

   auto pi = std::make_unique<int>(0);

   auto ppi = std::make_shared<std::unique_ptr<int>>(std::move(pi));

   std::function<void()> bar = [ppi] {
        **ppi = 5;
        std::cout << **ppi << std::endl;
   };

ここにライブ例

0
Taylor