web-dev-qa-db-fra.com

Comment créer une fonction std :: à partir d'une expression lambda de capture de mouvement?

J'essaie de créer un std::function À partir d'une expression lambda de capture de mouvement. Notez que je peux créer une expression lambda de capture de mouvement sans problème; ce n'est que lorsque j'essaie de l'envelopper dans un std::function que j'obtiens une erreur.

Par exemple:

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

Je vais vous expliquer pourquoi je veux écrire quelque chose comme ça. J'ai écrit une bibliothèque d'interface utilisateur qui, similaire à jQuery ou JavaFX, permet à l'utilisateur de gérer les événements souris/clavier en passant std::function S à des méthodes avec des noms comme on_mouse_down(), on_mouse_drag(), Push_undo_action(), etc.

De toute évidence, le std::function Que je veux transmettre devrait idéalement utiliser une expression lambda de capture de mouvement, sinon je dois recourir à l'idiome laid "libération/acquisition-en-lambda" que j'utilisais lorsque C++ 11 était la norme:

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

Notez que l'appel de baz deux fois serait une erreur dans le code ci-dessus. Cependant, dans mon code, cette fermeture est garantie d'être appelée exactement une fois.

BTW, dans mon vrai code, je ne passe pas un std::unique_ptr<int>, Mais quelque chose de plus intéressant.

Enfin, j'utilise Xcode6-Beta4 qui utilise la version suivante de clang:

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

Nécessite: F doit être CopyConstructible. f doit être Callable pour les types d'argument ArgTypes et renvoyer le type R. Le constructeur de copie et le destructeur de A ne doivent pas lever d'exceptions.

§20.9.11.2.1 [func.wrap.func.con]

Notez que operator = Est défini en fonction de ce constructeur et swap, donc les mêmes restrictions s'appliquent:

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

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

§20.9.11.2.1 [func.wrap.func.con]

Donc, pour répondre à votre question: Oui, il est possible de construire un std::function À partir d'un lambda de capture de mouvement (car cela spécifie uniquement comment le lambda capture), mais il est pas possible de construire un std::function à partir d'un type de mouvement uniquement (par exemple un lambda de capture de mouvement qui déplace-capture quelque chose qui n'est pas constructible par copie).

Comme std::function<?> doit effacer le constructeur de copie de l'objet invocable stocké, vous ne pouvez pas le construire à partir d'un type à déplacement uniquement. Votre lambda, car il capture un type de déplacement uniquement par valeur, est un type de déplacement uniquement. Donc ... vous ne pouvez pas résoudre votre problème. std::function ne peut pas stocker votre lambda.

Du moins pas directement.

Il s'agit de C++, nous contournons simplement le problème.

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

maintenant que ce qui précède est fait, nous pouvons résoudre votre problème.

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

La sémantique d'un shared_function est légèrement différent des autres fonctions, car une copie de celui-ci partage le même état (y compris lorsqu'il est transformé en std::function) comme l'original.

Nous pouvons également écrire une fonction de coup de feu à déplacement unique:

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

qui prend en charge même les types non mobiles via le emplace_as<T> tag.

exemple en direct .

Notez que vous devez évaluer () dans un contexte rvalue (c'est-à-dire après un std::move), comme un destructeur silencieux () semblait grossier.

Cette implémentation n'utilise pas SBO, car si elle le faisait, elle exigerait que le type stocké soit mobile, et ce serait plus de travail (pour moi) de démarrer.

26

Voici une solution plus simple:

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

exemple en direct ici

0
Taylor