Using boost::future with “then” continuations

后端 未结 4 1558
爱一瞬间的悲伤
爱一瞬间的悲伤 2020-12-24 12:35

The C++ 11 std::future lacks a then method to attach continuations to the future.

The Boost boost::future provides this, and t

相关标签:
4条回答
  • 2020-12-24 13:14

    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 define BOOST_THREAD_PROVIDES_FUTURE if you want to use boost::future. When BOOST_THREAD_VERSION>=3 define BOOST_THREAD_DONT_PROVIDE_FUTURE if you want to use boost::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.

    0 讨论(0)
  • 2020-12-24 13:20

    There may be a faster and easier way to use continuations than std::future or boost::future. Both of them have been criticized for being slow due to several reasons. See e.g. this presentation.

    The solution proposed in the presentation is implemented in github as a header-only library. You can chain any number of continuations and avoid implicit heap allocations. Also exceptions can be caught normally.

    Here is an example where two sets of continuations are run in parallel:

    #include <iostream>
    #include <cmath>
    #include <string>
    
    #include "Lazy.h"
    
    int main()
    {
        int iInput = 10;
    
        // Future #1: input an integer, then take square root, then convert double to string
        auto f = Lazy::future<std::string>(iInput).
                    then([](auto x) { return std::sqrt(double(x)); }).
                    then([](auto x) { return std::to_string(x); }).
                    finalize();
    
        // Future #2: input an integer, then square it, then convert int to string
        auto g = Lazy::future<std::string>(iInput).
                    then([](auto x){ return x*x; }).
                    then([](auto x){ return std::to_string(x);}).
                    finalize();
    
        // Launch the tasks...
        std::cout << "Calling f.run...\n";
        auto f_run = f.run();
        std::cout << "Calling g.run...\n";
        auto g_run = g.run();
    
        // Do something else while f and g are running...
        // ... then get the results.
        try {
            std::string strSqrt = f.get(f_run);
            std::string strSquare = g.get(g_run);
    
            std::cout << "Future f returned " << strSqrt << "\n";
            std::cout << "Future g returned " << strSquare << "\n";
        }
        catch (...) {
            // Deal with an exception here.
        }
    }
     /* Output:
    Calling f.run...
    Calling g.run...
    Future f returned 3.162278
    Future g returned 100
    */
    
    0 讨论(0)
  • 2020-12-24 13:22

    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 tuples -- 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::tuples. Oh well, later revision can handle that.

    0 讨论(0)
  • 2020-12-24 13:34

    This defining of macros seems to work for very small trivial programs however, it does not work well for large programs. In particular, some other file in the include path can incidentally include boost/thread.hpp or boost/thread/future.hpp. This can even come from an include in a third party library. As a result it breaks the usage of the macros as the header gets included before the macros are defined. Is there a way when building boost to tell boost to define these macros in one of its config.hpp files so that this problem can be avoided?

    0 讨论(0)
提交回复
热议问题