Improvements to std::future<T> and Related APIs

General

The extensions proposed here are an evolution of the functionality of std::future and std::shared_future. The extensions enable wait free composition of asynchronous operations. Class templates std::promise, std::packaged_task and function template std::async are also updated to be compatible with the updated std::future.

Header <experimental/future> synopsis

An additional editorial fix as in Fundamental v1 TS is applied in the declaration of swap for packaged_task
#include <future>

namespace std {
  namespace experimental {
  inline namespace concurrency_v1 {

    template <class R> class promise;
    template <class R> class promise<R&>;
    template <> class promise<void>;

    template <class R>
    void swap(promise<R>& x, promise<R>& y) noexcept;

    template <class R> class future;
    template <class R> class future<R&>;
    template <> class future<void>;
    template <class R> class shared_future;
    template <class R> class shared_future<R&>;
    template <> class shared_future<void>;

    template <class> class packaged_task; // undefined
    template <class R, class... ArgTypes>
    class packaged_task<R(ArgTypes...)>;

    template <class R, class... ArgTypes>
    void swap(packaged_task<R(ArgTypes...)>&, packaged_task<R(ArgTypes...)>&) noexcept;

    template <class F, class... Args>
      future<result_of_t<decay_t<F>(decay_t<Args>...)>>
      async(F&& f, Args&&... args);
    template <class F, class... Args>
      future<result_of_t<decay_t<F>(decay_t<Args>...)>>
      async(launch policy, F&& f, Args&&... args);

    template <class T>
    future<decay_t<T>> make_ready_future(T&& value);
    future<void> make_ready_future();

    future<T> make_exceptional_future(exception_ptr value);
    template <class T, class E>
    future<T> make_exceptional_future(E ex);

    template <class InputIterator>
      see below when_all(InputIterator first, InputIterator last);
    template <class... Futures>
      see below when_all(Futures&&... futures);

    template <class InputIterator>
      see below when_any(InputIterator first, InputIterator last);
    template <class... Futures>
      see below when_any(Futures&&... futures);

    template <class InputIterator>
      see below when_any_back(InputIterator first, InputIterator last);

  } // namespace concurrency_v1
  } // namespace experimental

  template <class R, class Alloc>
  struct uses_allocator<experimental::promise<R>, Alloc>;

  template <class R, class Alloc>
  struct uses_allocator<experimental::packaged_task<R>, Alloc>;

} // namespace std

Changes to cClass template future

The specification of all declarations within this sub-clause and its sub-clauses are the same as the corresponding declarations, as specified in , unless explicitly specified otherwise.

To the class declaration found in paragraph 3, add the following to the public functions: bool is_ready() const; future(future<future<R>>&& rhs) noexcept; template<typename F> see below then(F&& func); template<typename F> see below then(launch policy, F&& func);
namespace std {
  namespace experimental {
  inline namespace concurrency_v1 {

    template <class R>
    class future {
    public:
      future() noexcept;
      future(future &&) noexcept;
      future(const future& rhs) = delete;
      future(future<future<R>>&& rhs) noexcept;
      ~future();
      future& operator=(const future& rhs) = delete;
      future& operator=(future&&) noexcept;
      shared_future&<R> share();

      // retrieving the value
      see below get();

      // functions to check state
      bool valid() const noexcept;
      bool is_ready() const;

      void wait() const;
      template <class Rep, class Period>
        future_status wait_for(const chrono::duration<Rep, Period>& rel_time) const;
      template <class Clock, class Duration>
        future_status wait_until(const chrono::time_point<Clock, Duration>& abs_time) const;

      // continuations
      template<class F>
        see below then(F&& func);
      template<class F>
        see below then(launch policy, F&& func);

    };

  } // namespace concurrency_v1
  } // namespace experimental
  } // namespace std

In between paragraphs 9 and 10, add the following:

future(future<future<R>>&& rhs) noexcept; Constructs a future object from the shared state referred to by rhs and unwrapping the inner future. The future becomes ready when one of the following occurs:
  • Both the outer and the inner futures are ready. The future inherits the value or the exception from the inner future.
  • The outer future is ready but the inner future is invalid. The future gets an exception of type std::future_error, with an error code of std::future_errc::broken_promise.
  • valid() returns the same value as rhs.valid() prior to the constructor invocation.
  • valid() == true.
  • rhs.valid() == false.

After paragraph 26, add the following:

The member function template then provides a mechanism for attaching a continuation to a future object, that will be executed as specified below.

template<typenameclass F> see below then(F&& func); template<typenameclass F> see below then(launch policy, F&& func); INVOKE(func, *this) shall be a valid expression. The two functions differ only by input parameters. The first only takes a callable object which accepts a future object as a parameter. The second function takes a launch policy as the first parameter and a callable object as the second parameter. Both functions create a shared state that is associated with the returned future object. The further behavior of the functions is as follows.
  • The continuation INVOKE(DECAY_COPY (std::forward<F>(func))) is called when the object's shared state is ready (has a value or exception stored).
  • The continuation launches according to the specified launch policy, if it is provided.
  • When the launch policy is not provided the continuation inherits the parent's launch policy.
  • Any value returned from the continuation is stored as the result in the shared state of the resulting future. Any exception propagated from the execution of the continuation is stored as the exceptional result in the shared state of the resulting future.
  • If the parent was created with std::promise or with a packaged_task (has no associated launch policy), the continuation behaves the same as in the second overload with a policy argument of launch::async | launch::deferred and the same argument for func.
  • If the parent has a policy of launch::deferred, then it is filled by calling wait() or get() on the resulting future.
    auto f1 = async(launch::deferred, [] { return 1; });
    
    auto f2 = f1.then([](future<int> n) { return 2; });
    
    f2.wait(); // execution of f1 starts here, followed by f2
          
The return type of then depends on the return type of the closure func as defined below:
  • When result_of_t<decay_t<F>(future<R>)> is future<R2>, the function returns future<R2>.
  • Otherwise, the function returns future<result_of_t<decay_t<F>(future<R>)>>.

    The first rule above is called the implicit unwrapping. Without this rule, the return type of then taking a closure returning a future<R> would have been future<future<R>>. This rule avoids such nested future objects. The type of f2 below is future<int> and not future<future<int>>:

    future<int> f1 = g();
    future<int> f2 = f1.then([](future<int> f) {
                        future<int> f3 = h();
                        return f3;
                     });
    
  • Unlike futures returned from std::async (see paragraph 4), the destructor of the future object returned from then will not block.
  • The future object is moved to the parameter of the continuation function.
  • valid() == false on the original future; valid() == true on the future returned from then. object immediately after it returns. In case of implicit unwrapping, the validity of the future returned from then cannot be established until after the completion of the continuation. If it is not valid, the resulting future becomes ready with an exception of type std::future_error, with an error code of std::future_errc::broken_promise.
bool is_ready() const; true if the shared state is ready, false if it isn't.

Changes to cClass template shared_future

The specification of all declarations within this sub-clause and its sub-clauses are the same as the corresponding declarations, as specified in , unless explicitly specified otherwise.

To the class declaration found in paragraph 3, add the following to the public functions:


bool is_ready() const;

template<typename F>
see below then(F&& func);

template<typename F>
see below then(launch policy, F&& func);

namespace std {
  namespace experimental {
  inline namespace concurrency_v1 {

    template <class R>
    class shared_future {
    public:
      shared_future() noexcept;
      shared_future(const shared_future &) noexcept;
      shared_future(future &&) noexcept;
      shared_future(shared_future &&) noexcept;
      ~shared_future();
      shared_future& operator=(const shared_future& rhs);
      shared_future& operator=(shared_future&&) noexcept;

      // retrieving the value
      see below get();

      // functions to check state
      bool valid() const noexcept;
      bool is_ready() const;

      void wait() const;
      template <class Rep, class Period>
        future_status wait_for(const chrono::duration<Rep, Period>& rel_time) const;
      template <class Clock, class Duration>
        future_status wait_until(const chrono::time_point<Clock, Duration>& abs_time) const;

      // continuations
      template<class F>
        see below then(F&& func) const;
      template<class F>
        see below then(launch policy, F&& func) const;

    };

  } // namespace concurrency_v1
  } // namespace experimental
  } // namespace std

After paragraph 10, add the following:

shared_future(future<shared_future<R>>&& rhs) noexcept; Constructs a shared_future object from the shared state referred to by rhs. The shared_future becomes ready when one of the following occurs:
  • Both the outer future and the inner shared_future are ready. The shared_future inherits the value or the exception from the inner shared_future.
  • The outer future is ready but the inner shared_future is invalid. The shared_future gets an exception of type std::future_error, with an error code of std::future_errc::broken_promise.
  • valid() returns the same value as rhs.valid() prior to the constructor invocation.
  • valid() == true.
  • rhs.valid() == false.

After paragraph 28, add the following:

The member function template then provides a mechanism for attaching a continuation to a shared_future object, that will be executed as specified below.

template<typenameclass F> see below then(F&& func) const; template<typenameclass F> see below then(launch policy, F&& func) const; INVOKE(func, *this) shall be a valid expression. The two functions differ only by input parameters. The first only takes a callable object which accepts a shared_future object as a parameter. The second function takes a launch policy as the first parameter and a callable object which accepts a shared_future object as a parameter, as the second parameter. Both functions create a shared state that is associated with the returned future object. The further behavior of the functions is as follows.
  • The continuation INVOKE(DECAY_COPY (std::forward<F>(func)), *this) is called when the object's shared state is ready (has a value or exception stored).
  • The continuation launches according to the specified policy, if it is provided.
  • When the launch policy is not provided the continuation inherits the parent's launch policy.
  • Any value returned from the continuation is stored as the result in the shared state of the resulting future. Any exception propagated from the execution of the continuation is stored as the exceptional result in the shared state of the resulting future.
  • If the parent was created with std::promise (has no associated launch policy), the continuation behaves the same as in the second function with a policy argument of launch::async | launch::deferred and the same argument for func.
  • If the parent has a policy of launch::deferred, then it is filled by calling wait() or get() on the resulting shared_future. This is similar to future. See example in .
The return type of then depends on the return type of the closure func as defined below:
  • When result_of_t<decay_t<F>(shared_future<R>)> is future<R2>, the function returns future<R2>.
  • Otherwise, the function returns future<result_of_t<decay_t<F>(shared_future<R>)>>.

    This is the same as in future. See the notes on future::then return type in .

  • Unlike futures returned from std::async (see paragraph 4), the destructor of the future object returned from then will not block.
  • The shared_future passed to the continuation function is a copy of the original shared_future.
  • valid() == true on the original shared_future object. valid() == true on the shared_future returned from then. In case of implicit unwrapping, the validity of the future returned from then cannot be established until after the completion of the continuation. In such case, the resulting future becomes ready with an exception of type std::future_error, with an error code of std::future_errc::broken_promise.
bool is_ready() const; true if the shared state is ready, false if it isn't. If the function has returned true, all subsequent invocations on the same shared_future object will also return true.

Class template promise

The specification of all declarations within this sub-clause and its sub-clauses are the same as the corresponding declarations, as specified in , unless explicitly specified otherwise.

The future returned by the function get_future is the one defined in the experimental namespace ().

Class template packaged_task

The specification of all declarations within this sub-clause and its sub-clauses are the same as the corresponding declarations, as specified in , unless explicitly specified otherwise.

The future returned by the function get_future is the one defined in the experimental namespace ().

Function template async

The specification of all declarations within this sub-clause and its sub-clauses are the same as the corresponding declarations, as specified in , unless explicitly specified otherwise.

The future returned by the function async is the one defined in the experimental namespace ().

Function template when_all

A new section 30.6.10 shall be inserted at the end of . Below is the content of that section.

The function template when_all creates a future object that becomes ready when all elements in a set of future and shared_future objects become ready.

template <class InputIterator> see below future<vector<typename iterator_traits<InputIterator>::value_type>> when_all(InputIterator first, InputIterator last); template <typenameclass... TFutures> see below future<tuple<decay_t<Futures>...>> when_all(TFutures&&... futures);
  • For the first overload, iterator_traits<InputIterator>::value_type shall be convertible to future<R> or shared_future<R>, but not both. If any of the future<R> or shared_future<R> objects are in invalid state (i.e. valid() == false), the behavior is undefined.
  • For the second overload, T is of type future<R> or shared_future<R> let Fi be the ith type in Futures, Ui be remove_reference_t<Fi>, and fi be the ith parameter in the function parameter pack futures, where all indexing is zero-based. Then each Ui shall be the type future<Ri> or (possibly const) shared_future<Ri>; and fi.valid() shall be true for all i.
  • There are two variations of when_all. The first version takes a pair of InputIterators. The second takes any arbitrary number of future<R0> and shared_future<R1> objects, where R0 and R1 need not be the same type.
  • Calling the first signature of when_all where InputIterator first equals last, returns a future with an empty vector that is immediately ready.
  • Calling the second signature of when_allwhen_any with no arguments returns a future<tuple<>> that is immediately ready.
  • Each future and shared_future is waited upon and then copied into the collection of the output (returned) future, maintaining the order of the futures in the input collection.
  • If any of the futures or shared_futures supplied to a call to when_all refer to deferred tasks that have not started execution, those tasks are executed before the call to when_all returns.
  • The call to when_all does not wait for non-deferred tasks, or deferred tasks that have already started executing elsewhere, to complete before returning.
  • Once all the futures and shared_futures supplied to the call to when_all are ready, the futures are moved, and the shared_futures are copied, into, correspondingly, futures or shared_futures of a new collection, which can be either tuple or a vector as described below. The order of the objects in the collection matches the order of the arguments supplied to when_all.
  • The collection is then stored as the result in a newly created shared state.
  • A new future object that refers to the shared state is created. The exact type of the future is further described below.
  • The future returned by when_all will not throw an exception, but the futures and shared_futures held in the output collection may.
  • valid() == true.
  • All input future<T>s valid() == false.
  • All inputoutput shared_future<T> valid() == true.
  • A future object that becomes ready when all the input futures/shared_futures are ready.
  • future<tuple<>> if when_all is called with zero arguments.
  • future<vector<future<R>>> if the input cardinality is unknown at compile and the iterator pair yields future<R>. R may be void. The order of the future in the output vector will be the same as given by the input iterator.
  • future<vector<shared_future<R>>> if the input cardinality is unknown at compile time and the iterator pair yields shared_future<R>. R may be void. The order of the future in the output vector will be the same as given by the input iterator.
  • future<tuple<future<R0>, future<R1>, future<R2>...>> if inputs are fixed in number. The inputs can be any arbitrary number of future and shared_future objects. The type of the element at each position of the tuple corresponds to the type of the argument at the same position. Any of R0, R1, R2, etc. maybe void.

Function template when_any

A new section 30.6.11 shall be inserted at the end of . Below is the content of that section.

The function template when_any creates a future object that becomes ready when at least one element in a set of future and shared_future objects becomes ready.

template <class InputIterator> see below future<vector<typename iterator_traits<InputIterator>::value_type>> when_any(InputIterator first, InputIterator last); template <typenameclass... TFutures> see below future<tuple<decay_t<Futures>...>> when_any(TFutures&&... futures);
  • For the first overload, iterator_traits<InputIterator>::value_type shall be convertible to future<R> or shared_future<R>, but not both. If any of the future<R> or shared_future<R> objects are in invalid state (i.e. valid() == false), the behavior is undefined.
  • For the second overload, T is of type future<R> or shared_future<R> let Fi be the ith type in Futures, Ui be remove_reference_t<Fi>, and fi be the ith parameter in the function parameter pack futures, where all indexing is zero-based. Then each Ui shall be the type future<Ri> or (possibly const) shared_future<Ri>; and fi.valid() shall be true for all i.
  • There are two variations of when_any. The first version takes a pair of InputIterators. The second takes any arbitrary number of future<R> and shared_future<R> objects, where R need not be the same type.
  • Calling the first signature of when_any where InputIterator first equals last, returns a future with an empty vector that is immediately ready.
  • Calling the second signature of when_any with no arguments returns a future<tuple<>> that is immediately ready.
  • Each future and shared_future is waited upon. When at least one is ready, all the futures are copied into the collection of the output (returned) future, maintaining the order of the futures in the input collection.
  • Each of the futures and shared_futures supplied to when_any is checked in the order supplied. If a given future or shared_future refers to a deferred task that has not yet started execution, then no further futures or shared_futures are checked, and that task is executed.
  • The call to when_any does not wait for non-deferred tasks, or deferred tasks that have already started executing elsewhere, to complete before returning.
  • Once at least one of the futures or shared_futures supplied to the call to when_any are ready, the futures are moved, and the shared_futures are copied into, correspondingly, futures or shared_futures of a new collection, which can be either tuple or a vector as described below.
  • The order of the objects in the collection matches the order of the arguments supplied to when_any.
  • The collection is then stored as the result in the newly created shared state.
  • A new future object that refers to the shared state is created. The exact type of the future is further described below.
  • The future returned by when_any will not throw an exception, but the futures and shared_futures held in the output collection may.
  • valid() == true.
  • All input future<T>s valid() == false.
  • All inputoutput shared_future<T> valid() == true.
  • A future object that becomes ready when any of the input futures/shared_futures are ready.
  • future<tuple<>> if when_any is called with zero arguments.
  • future<vector<future<R>>> if the input cardinality is unknown at compile time and the iterator pair yields future<R>. R may be void. The order of the future in the output vector will be the same as given by the input iterator.
  • future<vector<shared_future<R>>> if the input cardinality is unknown at compile time and the iterator pair yields shared_future<R>. R may be void. The order of the future in the output vector will be the same as given by the input iterator.
  • future<tuple<future<R0>, future<R1>, future<R2>...>> if inputs are fixed in number. The inputs can be any arbitrary number of future and shared_future objects. The type of the element at each position of the tuple corresponds to the type of the argument at the same position. Any of R0, R1, R2, etc. maybe void.

Function template when_any_back

A new section 30.6.12 shall be inserted at the end of . Below is the content of that section.

The function template when_any_back creates a future object that becomes ready when at least one element in a set of future and shared_future objects becomes ready. The ready future or shared_future may be identified in constant time.

template <class InputIterator> see below future<vector<typename iterator_traits<InputIterator>::value_type>> when_any_back(InputIterator first, InputIterator last); iterator_traits<InputIterator>::value_type shall be convertible to future<R> or shared_future<R>. If any of the future<R> or shared_future<R> objects are in invalid state (i.e. valid() == false), the behavior is undefined.
  • Each future and shared_future is waited upon. When at least one is ready, all the future are copied into the collection of the output (returned) future.
  • Each of the futures and shared_futures supplied to when_any_back is checked in the order supplied. If a given future or shared_future refers to a deferred task that has not yet started execution, then no further futures or shared_futures are checked, and that task is executed.
  • The call to when_any_back does not wait for non-deferred tasks, or deferred tasks that have already started executing elsewhere, to complete before returning.
  • Once at least one of the futures or shared_futures supplied to the call to when_any_back are ready, the futures are moved, and the shared_futures are copied into, correspondingly, futures or shared_futures of a new collection, which can be either tuple or a vector as described below.
  • If the collection passed into when_any_back is non-empty (i.e., first != last), the last future or shared_future in the output collection is guaranteed to be in the ready state. The order of other elements in the collection is unspecified.
  • The collection is then stored as the result in the shared state.
  • A new future object that refers to the newly created shared state is created. The exact type of the future is further described below.
  • The future returned by when_any_back will not throw an exception, but the futures and shared_futures held in the output collection may.
  • After the copy, the future or shared_future that was first detected as being ready swaps its position with that of the last element of the result collection, so that the ready future or shared_future may be identified in constant time. Only one future or shared_future is thus moved.
  • valid() == true.
  • All input future<T>s valid() == false.
  • All inputoutput shared_future<T> valid() == true.
  • A future object that becomes ready when any of the input futures/shared_futures are ready.
  • future<vector<future<R>>> if the input cardinality is unknown at compile time and the iterator pair yields future<R>. R may be void.
  • future<vector<shared_future<R>>> if the input cardinality is unknown at compile time and the iterator pair yields shared_future<R>. R may be void.

Function template make_ready_future

A new section 30.6.13 shall be inserted at the end of . Below is the content of that section.

template <typenameclass T> future<decay_t<T>> make_ready_future(T&& value); future<void> make_ready_future(); The value that is passed in to the function is moved to the shared state of the returned future if it is an rvalue. Otherwise the value is copied to the shared state of the returned future.
  • future<decay_t<T>>, if function is given a value of type T.
  • future<void>, if the function is not given any inputs.
  • Returned future<decay_t<T>>, valid() == true.
  • Returned future<decay_t<T>>, is_ready() == true.

Function template make_exceptional_future

A new section 30.6.13 shall be inserted at the end of . Below is the content of that section.

template <class T> future<T> make_exceptional_future(exception_ptr ex); As if promise<T> p; p.set_exception(ex); return p.get_future(); template <class T, class E> future<T> make_exceptional_future(E ex); As if promise<T> p; p.set_exception(make_exception_ptr(ex)); return p.get_future();