Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to move future into lambda-expression

I'm using Visual Studio 2013 and i want achieve this line of code

 f = p.get_future();
 auto task =[f = std::move(f)](){
   //use f
 };

I'm aware of the solution here, but unfortunately this doesn't compile under VS2013 (error C2558 no copy-constructor available).

like image 933
user1235183 Avatar asked Oct 19 '22 07:10

user1235183


1 Answers

You can use a shared_future. That is easiest.

However, that doesn't help you move. If you really need to move, we can do it with the help of a move_helper function and class:

template<class T, class F=void>
struct move_helper_t {
  T t;
  F f;
  template<class...Args>
  auto operator()(Args&&...args)
  ->typename std::result_of< F&(T&, Args...) >::type
  {
    return f(t, std::forward<Args>(args)...);
  }

  // force right-associativity of `->*`, and
  // invert the stack to get the arguments in the "right" order:
  template<class F1,
    class R0=move_helper_t< T, typename std::decay<F1>::type >
  >
  auto operator->*(F1&& f1)
  -> decltype(
    std::declval<F>()->*
    std::declval<R0>()
  )
  {
    return
      std::move(f)->*
      R0{ std::forward<T>(t), std::forward<F1>(f1) };
  }
};
template<class T>
struct move_helper_t<T,void> {
  T t;
  template<class F>
  auto operator->*(F&& f)
  -> move_helper_t<T, typename std::decay<F>::type>
  {
    return {std::forward<T>(t), std::forward<F>(f)};
  }
};

template<class T>
move_helper_t<std::decay_t<T>>
move_helper( T&& t ) {
  return {std::forward<T>(t)};
}

In MSVC 2013 you may have to declare a constructor in move_helper_t. I do not recall how well written their return {} code was.

f = p.get_future();
task =
  move_helper(std::move(f)) ->*
  [](std::future<int>& f){
    //use f
  };

->* binds the move_helper to the lambda. It then returns a callable object which will be passed the std::future<int>& as the first argument when invoked.

Because of how it is written, you can even chain it:

auto f = p.get_future();
auto f2 = p2.get_future();
task =
  move_helper(std::move(f)) ->*
  move_helper(std::move(f2)) ->*
  [](std::future<int>& f, std::future<char>& f2){
    //use f
  };

to move more than one argument into the lambda.

In both cases, task can be invoked by task() -- the ->* operation binds the lambda up and passes the futures when invoked.

Live example.

Note that this solves the problem of moving the future into the lambda. If you want to store the lambda in a std::function, this will not help you, as functions must be copyable.

template<class F>
struct shared_function {
  std::shared_ptr<F> pf;
  template<class ...Args>
  typename std::result_of<F&(Args...)>::type
  operator()(Args&&...args) const {
   return (*pf)(std::forward<Args>(args)...);
  }
};
template<class F,
  class dF=typename std::decay<F>::type
>
shared_function< dF >
make_shared_function( F&& f ) {
  return {std::make_shared<dF>(std::forward<F>(f))};
}

this takes a movable lambda and wraps it in a shared pointer and exposes operator() for you. But first taking a future moving it into a lambda via the above technique, and then wrapping that lambda in a shared function to pass it to a std::function is ridiculous: just use a shared_future in the first place.

As an aside, in theory, a packaged_task only requires move, but I am uncertain if MSVC2013 supports that requirement.

like image 151
Yakk - Adam Nevraumont Avatar answered Oct 21 '22 03:10

Yakk - Adam Nevraumont