Подтвердить что ты не робот

Как создать функцию std:: из выражения лямбда-переноса?

Я пытаюсь создать 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);

Я объясню, почему я хочу написать что-то вроде этого. Я написал библиотеку UI, которая, подобно jQuery или JavaFX, позволяет пользователю обрабатывать события мыши/клавиатуры, передавая std::function методам с именами типа on_mouse_down(), on_mouse_drag(), push_undo_action() и т.д.

Очевидно, что std::function, который я хочу передать, должен в идеале использовать выражение лямбда для перемещения, в противном случае мне нужно прибегнуть к уродливой идиоме "release/приобретать-в-лямбда", которую я использовал, когда С++ 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
4b9b3361

Ответ 1

template<class F> function(F f);

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

Требуется: F должно быть CopyConstructible. F должен быть Callable для типов аргументов ArgTypes и возвращать тип R. Конструктор копирования и деструктор А не должны исключать исключения.

§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 с захватом перемещения (поскольку это только указывает, как захватывается лямбда), но это не возможно постройте a std::function из типа только для перемещения (например, перехватывающий захват лямбда, который перемещает - захватывает то, что не копирует конструкцию).

Ответ 2

Поскольку 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> типы через emplace_as<T>.

живой пример.

Обратите внимание, что вы должны оценить () в контексте rvalue (т.е. После std::move), поскольку молчаливый деструктив () казался грубым.

Эта реализация не использует SBO, потому что, если бы он это сделал, он потребовал, чтобы хранимый тип был перемещаемым, и это потребовало бы больше работы (для меня) для загрузки.

Ответ 3

Вот более простое решение:

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

Живой пример здесь