30

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

The Boost boost::future provides this, and there is an example (which I can't get running)

I am simply unable to compile:

#include <iostream>
#include <string>
#include <boost/thread/future.hpp>

boost::future<int> join2(const std::string& realm) {
   boost::promise<int> p;
   p.set_value(23);
   return p.get_future();
}

int main () {
   boost::future<int> f = join2("realm1");

   // here, I'd like to use f.then(..)
   f.wait();
   std::cout << f.get() << std::endl;
}

When compiling

clang++ -o test5.o -c -std=c++11 -stdlib=libc++ \
   -I/home/oberstet/boost_1_55_0 test5.cpp

this bails out with

test5.cpp:30:1: error: unknown type name 'future'
future<int> join(const std::string& realm) {
...

I am feeling stupid;) What is going on? I am using clang 3.4 with libc++ and Boost 1.55 (unmodified vanilla sources from Boost website).

Would be great to get a hint, probably also with an example of how to modify the example using .then(..) to print out the result.

Solution (kudos @dyp):

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

seems to be required when compiling for C++ 11 (which provides future), but one wants to use Boost future nevertheless.

For actually using continuations, another define is necessary: BOOST_THREAD_PROVIDES_FUTURE_CONTINUATION.

Here is a complete example

#include <iostream>
#include <string>

#define BOOST_THREAD_PROVIDES_FUTURE
#define BOOST_THREAD_PROVIDES_FUTURE_CONTINUATION

#include <boost/thread/future.hpp>

using namespace boost;


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

   future<std::string> f2 = f1.then([](future<int> f) {

      std::cout << f.get() << std::endl; // here .get() won't block
      return std::string("sgfsdfs");
   });
}
oberstet
  • 21,353
  • 10
  • 64
  • 97
  • You would need `std::future`, not `future`, for the C++ standard library version. – juanchopanza Mar 23 '14 at 22:35
  • I have `using namespace` directives .. – oberstet Mar 23 '14 at 22:39
  • 2
    You are not using them correctly. Anyway, what is the point of your code? If you want to use `then`, then use `boost` and forget all the define trickery. – juanchopanza Mar 23 '14 at 22:40
  • I have removed the namespace and ifdefs. It does not compile. So could you point me at my mistake please? – oberstet Mar 23 '14 at 22:44
  • 1
    There's a weird `#define` in `boost/thread/future.hpp`: `#if defined BOOST_THREAD_PROVIDES_FUTURE #define BOOST_THREAD_FUTURE future #else #define BOOST_THREAD_FUTURE unique_future #endif` See [boost docs: unique_future versus future](http://www.boost.org/doc/libs/1_55_0/doc/html/thread/build.html#thread.build.configuration.future) – dyp Mar 23 '14 at 22:45
  • Alright, that does the trick! `#define BOOST_THREAD_PROVIDES_FUTURE` before the include. Thanks! – oberstet Mar 23 '14 at 22:46
  • If you define `BOOST_THREAD_VERSION=4`, Boost.Thread will automatically define many macros related to experimental features, including `BOOST_THREAD_PROVIDES_FUTURE`. See https://github.com/boostorg/thread/blob/855e56076b68e4e45272fabb18090377a02830c7/include/boost/thread/detail/config.hpp#L226 – Emile Cormier Apr 26 '15 at 20:25

4 Answers4

21

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.

dyp
  • 38,334
  • 13
  • 112
  • 177
  • 5
    For actually using continuations, another define (undocumented) is necessary: `BOOST_THREAD_PROVIDES_FUTURE_CONTINUATION`. – oberstet Mar 24 '14 at 00:09
  • 3
    A simpler option is to compile with `-DBOOST_THREAD_VERSION=4`, which will enable the latest version of the Boost.Thread API, including exposing futures as `boost::future` rather than `boost::unique_fututre`. – James Henstridge May 06 '16 at 06:59
  • @JamesHenstridge Yes, might make more sense. Thanks. – dyp May 06 '16 at 12:03
8

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.

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
  • 1
    `as_well` should be called `meanwhile`. – Yakk - Adam Nevraumont Mar 24 '14 at 05:16
  • 1
    Interesting! Does that follow the proposal http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2013/n3634.pdf ? This is what Boost claims they are following for `boost::future.then`. Btw: Why wasn't that included in C++11? I read it's even not planned for C++14. Why that? It's an incredible useful idiom for asynchronous _control flow_ .. – oberstet Mar 24 '14 at 11:26
  • 1
    Do you have a repo for that? GitHub? License? – oberstet Mar 24 '14 at 11:28
  • 1
    @oberstet Does not follow that proposal (at least not directly, as I have not read it). Not in a repo. I literally just wrote it in about an hour and posted it here. I believe there is a standard license for code posted to SO that is pretty liberal, as I posted it here it applies. The only goal was then-like behaviour (feed one future into another automatically) and infix (because prefix makes chains longer than 2 ugly) syntax. – Yakk - Adam Nevraumont Mar 24 '14 at 12:49
  • 3
    @oberstet The stack overflow license is less liberal than I originally thought. So: To the extent possible under law, I have associated [CC0](http://creativecommons.org/publicdomain/zero/1.0/) with the above source code and comments, and have waived all copyright and related or neighboring rights to this work. Enjoy. – Yakk - Adam Nevraumont Mar 24 '14 at 18:16
  • Awesome, thanks! I'll check it out, since I probably want to get this self-contained (no boost). But I am still wary overall with this stuff .. right now I am stuck on http://stackoverflow.com/questions/22617420/boostfuture-and-continuations-value-set-but-future-still-blocks. – oberstet Mar 24 '14 at 18:19
  • Haha, I've done a similar implementation (with the exception of using as the infix) – Viktor Sehr Oct 23 '19 at 07:03
1

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?

Chappelle
  • 65
  • 2
  • 9
  • 2
    Pass the define to the compiler on the command line (or via your build process), don't add it to the source code yourself, otherwise you'll get the problems you describe. –  Dec 18 '15 at 14:50
1

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
*/
Keijo
  • 176
  • 7