c++ - كيف يمكن إنشاء دالة std:: من تعبير lambda متحرك؟




c++14 (2)

أحاول إنشاء std::function من تعبير lambda متحرك. لاحظ أنه بإمكاني إنشاء تعبير lambda متحرك بدون مشاكل ؛ فقط عندما أحاول لفه في وظيفة 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);

سأشرح لماذا أريد أن أكتب شيئًا كهذا. لقد قمت بكتابة مكتبة UI ، والتي تشبه jQuery أو JavaFX ، تسمح للمستخدم بالتعامل مع أحداث الماوس / لوحة المفاتيح عن طريق تمرير std::function s إلى الأساليب مع أسماء مثل on_mouse_down() ، on_mouse_drag() ، push_undo_action() ، إلخ.

من الواضح أن std::function أريد أن أعبر يجب أن تستخدم بشكل مثالي تعبير lambda الذي يتحرك في التحريك ، وإلا سأحتاج إلى اللجوء إلى المصطلح القبيح "إطلاق / اكتساب in lambda" الذي كنت أستخدمه عندما كان C ++ 11 اساسي:

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

لاحظ أن استدعاء baz مرتين سيكون خطأ في التعليمات البرمجية أعلاه. ومع ذلك ، في رمز بلدي ، ويضمن هذا الإغلاق يسمى مرة واحدة بالضبط.

راجع للشغل ، في رمز بلدي الحقيقي ، وأنا لا يمر std::unique_ptr<int> ، ولكن شيئا أكثر إثارة للاهتمام.

أخيرًا ، أنا أستخدم Xcode6-Beta4 الذي يستخدم الإصدار التالي من 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

template<class F> function(F f);

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

يتطلب: F يجب أن تكون CopyConstructible . f يجب أن يكون ArgTypes لأنواع الوسيطة 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 من لامبدا يتحكم في التحريك (حيث أن هذا يحدد فقط كيفية التقاط lambda) ، ولكن ليس من الممكن إنشاء 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 ) باعتبارها النسخة الأصلية.

يمكننا أيضًا كتابة وظيفة إطلاق النار مرة واحدة فقط:

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> .

مثال حي .

ملاحظة لديك لتقييم () في سياق rvalue (أي ، بعد std::move ) ، كما بدا مدمرة صامت () وقحا.

هذا التطبيق لا يستخدم SBO ، لأنه إذا فعل ذلك فإنه سيطلب أن يكون النوع الذي تم تخزينه منقولة ، وسيكون العمل أكثر (بالنسبة لي) للإقلاع.





c++14