Using boost::future with "then" continuations
Boost.Thread comes in several versions of which you can choose via the BOOST_THREAD_VERSION
macro. Currently, the default is 2
.
Up to version 2 of Boost.Thread, the name boost::unique_future
was used for this class template (compare to boost::shared_future
). Probably because of the standardization of std::future
, more recent versions can use the name boost::future
. Starting with version 3
, boost::future
is the default name.
The selection which name is to be used is done via a preprocessor macro:
When
BOOST_THREAD_VERSION==2
defineBOOST_THREAD_PROVIDES_FUTURE
if you want to useboost::future
. WhenBOOST_THREAD_VERSION>=3
defineBOOST_THREAD_DONT_PROVIDE_FUTURE
if you want to useboost::unique_future
.
From boost docs: unique_future
vs future
So you can either explicitly enable boost::future
by using BOOST_THREAD_PROVIDES_FUTURE
or switch to a more modern version of Boost.Thread by setting BOOST_THREAD_VERSION
to 4
, for example.
If you would prefer using std::future
instead of boost::future
, you could just use this:
#include <iostream>
#include <thread>
#include <future>
#include <memory>
namespace later {
// infix operator boilerplate:
template<typename T> struct infix_tag {};
template<typename op, typename LHS>
struct partial {
std::future<LHS>&& lhs;
};
// note: moves lhs!
template<typename LHS, typename Op>
partial<Op, LHS> operator*( std::future<LHS>& lhs, infix_tag<Op> ) {
return { std::move(lhs) };
}
template<typename Op, typename LHS>
partial<Op, LHS> operator*( std::future<LHS>&& lhs, infix_tag<Op> ) {
return { std::move(lhs) };
}
template<typename Op, typename LHS, typename RHS, typename=void>
struct continue_t;
template<typename Op, typename LHS, typename RHS>
std::future< typename continue_t<Op, LHS, RHS>::type >
operator*( partial<Op, LHS>&& lhs, RHS&& rhs )
{
return continue_t<Op, LHS, RHS>()( std::move(lhs.lhs), std::forward<RHS>(rhs) );
}
// std::future<T> *then* lambda(T) support:
struct then_t:infix_tag<then_t> {};
static constexpr then_t then;
template<typename LHS, typename RHS>
struct continue_t<then_t, LHS, RHS, void> {
typedef typename std::result_of< RHS( LHS ) >::type type;
template<typename T, typename U>
std::future<type> operator()( std::future<T>&& lhs_, U&& rhs_ ) const {
auto lhs = std::make_shared<std::future<T>>( std::move(lhs_) );
auto rhs = std::make_shared<typename std::remove_reference<U>::type>( std::forward<U>(rhs_) );
return std::async( [lhs, rhs]()->type { return (*rhs)((*lhs).get()); });
}
};
template<typename RHS>
struct continue_t<then_t, void, RHS, void> {
typedef typename std::result_of< RHS() >::type type;
template<typename T, typename U>
std::future<type> operator()( std::future<T>&& lhs_, U&& rhs_ ) const {
auto lhs = std::make_shared<std::future<T>>( std::move(lhs_) );
auto rhs = std::make_shared<typename std::remove_reference<U>::type>( std::forward<U>(rhs_) );
return std::async( [lhs, rhs]()->type { lhs->get(); return (*rhs)(); });
}
};
// std::future<T> *as_well* lambda() support:
struct as_well_t:infix_tag<as_well_t> {};
static constexpr as_well_t as_well;
template<typename LHS, typename RHS>
struct continue_t<as_well_t, LHS, RHS, typename std::enable_if<!std::is_same<void, typename std::result_of< RHS() >::type>::value>::type> {
typedef std::tuple< LHS, typename std::result_of< RHS() >::type> type;
template<typename T, typename U>
std::future<type> operator()( std::future<T>&& lhs_, U&& rhs_ ) const {
auto lhs = std::make_shared<std::future<T>>( std::move(lhs_) );
auto rhs = std::make_shared<typename std::remove_reference<U>::type>( std::forward<U>(rhs_) );
return std::async( [lhs, rhs]()->type {
auto&& r = (*rhs)();
return std::make_tuple((*lhs).get(), std::forward<decltype(r)>(r));
});
}
};
template<typename LHS, typename RHS>
struct continue_t<as_well_t, LHS, RHS, typename std::enable_if<std::is_same<void, typename std::result_of< RHS() >::type>::value>::type> {
typedef LHS type;
template<typename T, typename U>
std::future<type> operator()( std::future<T>&& lhs_, U&& rhs_ ) const {
auto lhs = std::make_shared<std::future<T>>( std::move(lhs_) );
auto rhs = std::make_shared<typename std::remove_reference<U>::type>( std::forward<U>(rhs_) );
return std::async( [lhs, rhs]()->type {
(*rhs)();
return (*lhs).get();
});
}
};
template<typename RHS>
struct continue_t<as_well_t, void, RHS, typename std::enable_if<!std::is_same<void, typename std::result_of< RHS() >::type>::value>::type> {
typedef typename std::result_of< RHS() >::type type;
template<typename T, typename U>
std::future<type> operator()( std::future<T>&& lhs_, U&& rhs_ ) const {
auto lhs = std::make_shared<std::future<T>>( std::move(lhs_) );
auto rhs = std::make_shared<typename std::remove_reference<U>::type>( std::forward<U>(rhs_) );
return std::async( [lhs, rhs]()->type {
auto&& r = (*rhs)();
lhs->get();
return std::forward<decltype(r)>(r);
});
}
};
template<typename RHS>
struct continue_t<as_well_t, void, RHS, typename std::enable_if<std::is_same<void, typename std::result_of< RHS() >::type>::value>::type> {
typedef typename std::result_of< RHS() >::type type;
template<typename T, typename U>
std::future<type> operator()( std::future<T>&& lhs_, U&& rhs_ ) const {
auto lhs = std::make_shared<std::future<T>>( std::move(lhs_) );
auto rhs = std::make_shared<typename std::remove_reference<U>::type>( std::forward<U>(rhs_) );
return std::async( [lhs, rhs]()->type {
(*rhs)();
lhs->get();
return;
});
}
};
}
using later::then;
using later::as_well;
int main() {
std::future<int> computation = std::async( [](){ return 7; })
*then* [](int x) { return x+2; }
*as_well* []() { std::cout << "step 2\n"; }
*then* [](int x) { std::cout << x << "\n"; return x; }
*as_well* []() { return 3; }
*then* []( std::tuple<int, int> m ){ std::cout << std::get<0>(m) + std::get<1>(m) << "\n"; }
*as_well* []() { std::cout << "bah!\n"; return 3; };
computation.wait();
// your code goes here
return 0;
}
which is a little hacked together infix then library I just wrote.
It is far from perfect, because it does not continue the then
task within the future
: each then
or as_well
spawns a new task.
In addition, as_well
doesn't merge tuple
s -- if the left hand side std::future
is a std::future<std::tuple<blah, blah>>
, I should merge with it, rather than make a std::tuple
of std::tuple
s. Oh well, later revision can handle that.