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 and std::packaged_task 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 T>
      see below make_ready_future(T&& value);
    future<void> make_ready_future();

    template <class T>
      future<T> make_exceptional_future(exception_ptr ex);
    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 Sequence>
    struct when_any_result;

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

  } // 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

Class template future

The specifications of all declarations within this subclause and its subclauses are the same as the corresponding declarations, as specified in , unless explicitly specified otherwise.

namespace std {
  namespace experimental {
  inline namespace concurrency_v1 {

    template <class R>
    class future {
    public:
      future() noexcept;
      future(future &&) noexcept;
      future(const future&) = delete;
      future(future<future<R>>&&) noexcept;
      ~future();
      future& operator=(const future&) = 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);

    };

  } // namespace concurrency_v1
  } // namespace experimental
  } // namespace std
future(future<future<R>>&& rhs) noexcept; Constructs a future object from the shared state referred to by rhs. The future becomes ready when one of the following occurs:
  • Both the rhs and rhs.get() are ready. The value or the exception from rhs.get() is stored in the future's shared state.
  • rhs is ready but rhs.get() is invalid. An exception of type std::future_error, with an error condition of std::future_errc::broken_promise is stored in the future's shared state.
  • valid() == true.
  • rhs.valid() == false.

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

template <class F> see below then(F&& func); INVOKE(DECAY_COPY (std::forward<F>(func)), std::move(*this)) shall be a valid expression. The function creates a shared state that is associated with the returned future object. Additionally,
  • When the object's shared state is ready, the continuation INVOKE(DECAY_COPY(std::forward<F>(func)), std::move(*this)) is called on an unspecified thread of execution with the call to DECAY_COPY() being evaluated in the thread that called then.
  • 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.
When result_of_t<decay_t<F>(future<R>)> is future<R2>, for some type R2, the function returns future<R2>. Otherwise, the function returns future<result_of_t<decay_t<F>(future<R>)>>. The rule above is referred to as implicit unwrapping. Without this rule, the return type of then taking a callable 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;
                 });
valid() == false on the original future. valid() == true on the 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. If it is not valid, the resulting future becomes ready with an exception of type std::future_error, with an error condition of std::future_errc::broken_promise.
bool is_ready() const; true if the shared state is ready, otherwise false.

Class template shared_future

The specifications of all declarations within this subclause and its subclauses are the same as the corresponding declarations, as specified in , unless explicitly specified otherwise.

  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<R>&&) noexcept;
      shared_future(future<shared_future<R>>&& rhs) noexcept;
      ~shared_future();
      shared_future& operator=(const shared_future&);
      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;
    };

  } // namespace concurrency_v1
  } // namespace experimental
  } // namespace std
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 rhs and rhs.get() are ready. The value or the exception from rhs.get() is stored in the shared_future's shared state.
  • rhs is ready but rhs.get() is invalid. The shared_future stores an exception of type std::future_error, with an error condition of std::future_errc::broken_promise.
  • valid() == true.
  • rhs.valid() == false.

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

template <class F> see below then(F&& func) const; INVOKE(DECAY_COPY (std::forward<F>(func)), *this) shall be a valid expression. The function creates a shared state that is associated with the returned future object. Additionally,
  • When the object's shared state is ready, the continuation INVOKE(DECAY_COPY(std::forward<F>(func)), *this) is called on an unspecified thread of execution with the call to DECAY_COPY() being evaluated in the thread that called then.
  • 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.
When result_of_t<decay_t<F>(const shared_future&)> is future<R2>, for some type R2, the function returns future<R2>. Otherwise, the function returns future<result_of_t<decay_t<F>(const shared_future&)>>. This analogous to future. See the notes on the return type of future::then in . valid() == true on the original shared_future object. valid() == true on the 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 condition of std::future_errc::broken_promise.
bool is_ready() const; true if the shared state is ready, otherwise false.

Class template promise

The specifications of all declarations within this subclause and its subclauses 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 specifications of all declarations within this subclause and its subclauses 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 when_all

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> future<vector<typename iterator_traits<InputIterator>::value_type>> when_all(InputIterator first, InputIterator last); template <class... Futures> future<tuple<decay_t<Futures>...>> when_all(Futures&&... futures); All futures and shared_futures passed into when_all must be in a valid state (i.e. valid() == true).
  • The first overload shall not participate in overload resolution unless iterator_traits<InputIterator>::value_type is future<R> or shared_future<R> for some type R.
  • For the second overload, let Di be decay_t<Fi>, and let Ui be remove_reference_t<Fi> for each Fi in Futures. This function shall not participate in overload resolution unless for each i either Di is a shared_future<Ri> or Ui is a future<Ri>.
  • A new shared state containing a Sequence is created, where Sequence is either vector or tuple based on the overload, as specified above. A new future object that refers to that shared state is created and returned from when_all.
  • If the first overload is called with first == last, when_all returns a future with an empty vector that is immediately ready.
  • If the second overload is called with no arguments, when_all returns a future<tuple<>> that is immediately ready.
  • Otherwise, any futures are moved, and any shared_futures are copied into, correspondingly, futures or shared_futures of Sequence in the shared state.
  • The order of the objects in the shared state matches the order of the arguments supplied to when_all.
  • Once all the futures and shared_futures supplied to the call to when_all are ready, the resulting future, as well as the futures and shared_futures of the Sequence, are ready.
  • The shared state of the future returned by when_all will not store an exception, but the shared states of futures and shared_futures held in the shared state may.
  • For the returned future, valid() == true.
  • For all input futures, valid() == false.
  • For all input shared_futures, valid() == true.
A future object that becomes ready when all of the input futuresand shared_futures are ready.

Class template when_any_result

The library provides a template for storing the result of when_any.


template<class Sequence>
struct when_any_result {
    size_t index;
    Sequence futures;
};

Function template when_any

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> future<when_any_result<vector<typename iterator_traits<InputIterator>::value_type>>> when_any(InputIterator first, InputIterator last); template <class... Futures> future<when_any_result<tuple<decay_t<Futures>...>>> when_any(Futures&&... futures); All futures and shared_futures passed into when_all must be in a valid state (i.e. valid() == true).
  • The first overload shall not participate in overload resolution unless iterator_traits<InputIterator>::value_type is future<R> or shared_future<R> for some type R.
  • For the second overload, let Di be decay_t<Fi>, and let Ui be remove_reference_t<Fi> for each Fi in Futures. This function shall not participate in overload resolution unless for each i either Di is a shared_future<Ri> or Ui is a future<Ri>.
  • A new shared state containing when_any_result<Sequence> is created, where Sequence is a vector for the first overload and a tuple for the second overload. A new future object that refers to that shared state is created and returned from when_any.
  • If the first overload is called with first == last, when_any returns a future that is immediately ready. The value of the index field of the when_any_result is static_cast<size_t>(-1). The futures field is an empty vector.
  • If the second overload of is called with no arguments, when_any returns a future that is immediately ready. The value of the index field of the when_any_result is static_cast<size_t>(-1). The futures field is tuple<>.
  • Otherwise, any futures are moved, and any shared_futures are copied into, correspondingly, futures or shared_futures of the futures member of when_any_result<Sequence> in the shared state.
  • The order of the objects in the futures shared state matches the order of the arguments supplied to when_any.
  • Once at least one of the futures or shared_futures supplied to the call to when_any is ready, the resulting future is ready. Given the result future f, f.get().index is the position of the ready future or shared_future in the futures member of when_any_result<Sequence> in the shared state.
  • The shared state of the future returned by when_all will not store an exception, but the shared states of futures and shared_futures held in the shared state may.
  • For the returned future, valid() == true.
  • For all input futures, valid() == false.
  • For all input shared_futures, valid() == true.
  • A future object that becomes ready when any of the input futures and shared_futures are ready.

Function template make_ready_future

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

Let U be decay_t<T>. Then V is X& if U equals reference_wrapper<X>, otherwise V is U.

The function creates a shared state that is immediately ready and returns a future associated with that shared state. For the first overload, the type of the shared state is V and the result is constructed from std::forward<T>(value). For the second overload, the type of the shared state is void. For the returned future, valid() == true and is_ready() == true.

Function template make_exceptional_future

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