Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using boost::future with continuations and boost::when_all

I would like to use boost::future with continuations and boost::when_all / boost::when_any.

Boost trunk - not 1.55 - includes implementations for the latter (modeled after the proposal here, upcoming for C++14/17 and Boost 1.56).

This is what I have (and it does not compile):

#include <iostream>

#define BOOST_THREAD_PROVIDES_FUTURE
#define BOOST_THREAD_PROVIDES_FUTURE_CONTINUATION
#define BOOST_THREAD_PROVIDES_FUTURE_WHEN_ALL_WHEN_ANY
#include <boost/thread/future.hpp>

using namespace boost;

int main() {
   future<int> f1 = async([]() { return 1; });
   future<int> f2 = async([]() { return 2; });

   auto f3 = when_all(f1, f2);

   f3.then([](decltype(f3)) {
      std::cout << "done" << std::endl;
   });

   f3.get();
}

Clang 3.4 bails out with a this - here is an excerpt:

/usr/include/c++/v1/memory:1685:31: error: call to deleted constructor of 'boost::future<int>'
::new((void*)__p) _Up(_VSTD::forward<_Args>(__args)...);

Am I doing it wrong or is this a bug?

like image 409
oberstet Avatar asked Mar 26 '14 14:03

oberstet


1 Answers

The problem is that when_all may only be called with rvalue future or shared_future. From N3857:

template <typename... T> 
see below when_all(T&&... futures); 

Requires: T is of type future<R> or shared_future<R>.

Thanks to the reference-collapsing rules, passing an lvalue results in T being deduced to future<T>& in violation of the stated requirement. The boost implementation doesn't check this precondition so you get an error deep in the template code where what should be a move of an rvalue future turns into an attempted copy of an lvalue future.

You need to either move the futures into the when_all parameters:

auto f3 = when_all(std::move(f1), std::move(f2));

or avoid naming them in the first place:

auto f = when_all(async([]{return 1;}),
                  async([]{return 2;}));

Also, you must get the future returned from then instead of the intermediate future:

auto done = f.then([](decltype(f)) {
  std::cout << "done" << std::endl;
});

done.get();

since the future upon which you call then is moved into the parameter of the continuation. From the description of then in N3857:

Postcondition:

  • The future object is moved to the parameter of the continuation function

  • valid() == false on original future object immediately after it returns

Per 30.6.6 [futures.unique_future]/3:

The effect of calling any member function other than the destructor, the move-assignment operator, or valid on a future object for which valid() == false is undefined.

You could avoid most of these issues in c++14 by avoiding naming the futures at all:

when_all(
  async([]{return 1;}),
  async([]{return 2;})
).then([](auto&) {
  std::cout << "done" << std::endl;
}).get();
like image 52
Casey Avatar answered Sep 22 '22 15:09

Casey