Document Number: N2997=09-0187
Date: 2009-10-23
Project: Programming Language C++

Detlef Vollmann <dv@vollmann.ch>
Anthony Williams <anthony@justsoftwaresolutions.co.uk>

N2997: Issues on Futures (Rev. 1)

Revision history

This is a revision of N2967, with the following changes (mainly requested by the EWG).

Changes w.r.t. N2967

The wording was changed so that is_ready() can easily be removed. [Note to the editor: N2996 proposes (among others) to remove is_ready, has_value and has_exception. With the new wording this can easily be done.]

Rename unique_future to future.

future::get() now returns by value.
Also added that the returned value shall be moved if possible.

Added synchronization wording for atomic_future.

Removed template <class Allocator> promise(allocator_arg_t, const Allocator& a, promise& rhs); as no longer needed any more.

Added "Throws: nothing." to all move constructors.

Removed note from uses_allocator<promise>.

Added uses_allocator<packaged_task>.

Corrected text for UK331/1160.

Changed resolution for UK341 (swap paramter is correct).

Added external swap(promise, promise).

Not done: two changes proposed by Pablo to simplify the spezialization of futures and promise.

Separated issues/NBCs solved and issues/NBCs explicitely not addresses.

Moved UK340/1050 to non-addressed NBCs.

[End of revision history.]

Introduction

This paper addresses a lot of editor's notes in N2798 and NB comments on clause 30.5 Futures (now 30.6).

NB comments addressed in this paper

NB comment LWG issue Short description Resolution
UK 330 Reference to undefined constructible_with_allocator_prefix References were removed
UK 331 1160 exposition only for future_error constructor Constructor is now properly defined
UK 332 Missing introduction to future An introduction was added
UK 334 1047 Missing blocking specification for *_future::get Blocking was now specified
UK 335 1048 Moved out state of a future could not be detected valid() was added
UK 336 1161 Unnecessary future limitations Default construction and move assignment were added
JP 80 Typo in *_future::wait_for Corrected
UK 337 1162 shared_future should support an efficient move constructor Move constructor was added
UK 338 1163 shared_future is inconsistent with shared_ptr

Move constructor and move assignment and atomic_future were added.

The need for an atomic_future arose from the fact that the move constructor and both assigments made the shared_future much more error prone to use a single instance from multiple threads. But such use is required when more than one thread scans a global list of futures for results provided by worker threads.

UK 339 1049 promise move assignment goes into wrong direction Corrected
UK 342 1088 Non-member swap for promise::swap() Added
UK 343 1165 Unneeded promise move constructor Removed

NB comments not addressed in this paper, but recommended to be NAD

NB comment LWG issue Short description Resolution
JP 79 Concepts for uses_allocator NAD concept: We don't have concepts now
UK 333 Conceptifying *_future::get NAD concept: We don't have concepts now
UK 341 Wrong parameter type for promise::swap() NAD: It was generally decided to swap() by lvalue reference
JP 81 Conceptification of packaged_task NAD concept: We don't have concepts now, but uses_allocator trait was added

NB comments not addressed in this paper, still open

NB comment LWG issue Short description Resolution
UK 340 1050 Missing postcondition on promise::get_future() Intent accepted, but proposed wording

Proposed Wording

As in the rewording nearly every paragraph of [future] has changed, here is the new complete proposed wording for that clause.

Futures [futures]

Overview [futures.overview]

[futures] describes components that a C++ program can use to retrieve in one thread the result (value or exception) from a function that has run in another thread.

[Note: these components are not restricted to multi-threaded programs but can be useful in single-threaded programs as well. --end note]

Header <future> synopsis

namespace std {
    enum class future_errc
    {
        broken_promise,
        future_already_retrieved,
        future_already_initialized,
        promise_already_satisfied,
        no_state
    };
    template <> struct is_error_code_enum<future_errc> : public true_type {};
    constexpr error_code make_error_code(future_errc e);
    constexpr error_condition make_error_condition(future_errc e);
    extern const error_category* const future_category;
    class future_error;
    template <class R> class promise;
    template <class R> class promise<R&>;
    template <> class promise<void>;
    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 R> class atomic_future;
    template <class R> class atomic_future<R&>;
    template <> class atomic_future<void>;
    template <class> class packaged_task;
    template <class R, class... ArgTypes>
      class packaged_task<R(ArgTypes...)>;
}

Error handling [futures.errors]

extern const error_category* const future_category;
future_category shall point to a statically initialized object of a type derived from class error_category.
The object's default_error_condition and equivalent virtual functions shall behave as specified for the class error_category. The object's name virtual function shall return a pointer to the string "future".
constexpr error_code make_error_code(future_errc e);
Returns: error_code(static_cast<int>(e), *future_category).
constexpr error_condition make_error_condition(future_errc e);
Returns: error_condition(static_cast<int>(e), *future_category).

Class future_error [futures.future_error]

  namespace std
  {
    class future_error : public logic_error
    {
    public:
        future_error(error_code ec);
        const error_code& code() const throw();
        const char* what() const throw();
    };
  }
future_error(error_code ec);
Effects: Constructs an object of class future_error.
Postcondition: ec == code()
const error_code& code() const throw();
Returns: the value of ec that was passed to the object's constructor.
const char *what() const throw();
Returns: an NTBS incorporating code().message().

Associated State

If fully initialized, objects of type future ([future.future]), shared_future ([future.shared_future]), atomic_future ([future.atomic_future]), promise ([future.promise) and packaged_task ([future.task]) reference some state that is potentially shared between several such objects.

This associated state consists of some state information and some (possibly not yet evaluated) result, which can be a (possibly void) value or an exception.

The result of such an associated state can be set by

When the last reference to an associated state is given up, any resources held by this associated state are released.

An associated state is ready only if it holds a value or an exception ready for retrieval.

The functions that successfully set the stored result of an associated state synchronize with ([intro.multithread]) calls to member functions of other objects referring to the same associated state and such calls are serialized. The storage of the result (whether normal or exceptional) into the associated state happens-before ([intro.multithread]) that state is set to ready.

Accesses to the same associated state through member functions of future, shared_future, atomic_future, promise or packaged_task objects conflict ([intro.multithread]).

Class template promise [futures.promise]

  namespace std {
    template <class R>
    class promise {
    public:
      promise();
      template <class Allocator>
        promise(allocator_arg_t, const Allocator& a);
      promise(promise && rhs);
      promise(const promise& rhs) = delete;
      ~promise();
      promise & operator=(promise&& rhs);
      promise & operator=(const promise& rhs) = delete;
      void swap(promise& other);
      future<R> get_future();
      void set_value(const R& r);
      void set_value(see below);
      void set_exception(exception_ptr p);
    };
    void swap(promise<R>& x, promise<R>& y);
    template <typename R, class Alloc>
      struct uses_allocator<promise<R>, Alloc>;
  }

The implementation shall provide the template promise and two specializations, promise<R&> and promise<void>. These differ only in the argument type of the member function set_value, as set out in its description, below.

template <typename R, class Alloc>
  struct uses_allocator<promise<R>, Alloc>
   : true_type { };
Requires: Alloc shall be an Allocator ([allocator.requirements] 20.1.2)
promise();
template <class Allocator>
  promise(allocator_arg_t, const Allocator& a);
Effects: constructs a promise object and an associated state. The second constructor uses the allocator a to allocate memory for the associated state.
promise(promise && rhs);
Effects: Constructs a new promise object and transfers ownership of the associated state of rhs (if any) to the newly-constructed object.
Postcondition: rhs has no associated state.
Throws: nothing.
~promise();
Effects: If the state associated with *this is not ready, stores an exception of type future_error with an error code of broken_promise as-if by this->set_exception(copy_exception(future_error(future_errc::broken_promise))).
Destroys *this and releases its reference to its associated state if any. If this is the last reference to that associated state, destroys that state.
promise& operator=(promise&& rhs);
Effects: As-if:
std::promise<R>(std::move(rhs)).swap(*this);
Postcondition: rhs has no associated state. *this has the associated state of rhs prior to the assignment.
Returns: *this.
Throws: nothing.
void swap(promise& other);
Effects: Exchanges the associated states of *this and other.

Postcondition: *this has the same associated state (if any) as other prior to the call to swap. other has the same associated state (if any) as *this prior to the call to swap.

future<R> get_future();
Returns: a future<R> object with the same associated state as *this.
Throws: future_error if *this has no associated state, or if get_future() has already been called on a promise with the associated state of *this.
Error conditions:
void set_value(const R& r);
void promise::set_value(R&& r);
void promise<R&>::set_value(R& r);
void promise<void>::set_value();
Effects: Atomically stores r in the associated state and sets that state to ready. Any threads blocked in a call of a blocking function of any future that refers to the same associated state as *this are unblocked.
Throws: future_error if its associated state is already ready.
Error conditions:
Synchronization: Calls to set_value and set_exception on a single promise object are serialized.
[Note: And they synchronize and serialize with other functions through the referred associated state. --end note]
void set_exception(exception_ptr p);
Effects: Atomically stores p in the associated state and sets that state to ready. Any threads blocked in a call of a blocking function of any future that refers to the same associated state as *this are unblocked.
Throws: future_error if its associated state is already ready.
Error conditions:
Synchronization: Calls to set_value and set_exception on a single promise object are serialized.
[Note: And they synchronize and serialize with other functions through the referred associated state. --end note]
void swap(promise<R>& x, promise<R>& y);
Effects: x.swap(y)

Class template future [futures.future]

The class template future defines a type for asynchronous return objects which do not share their associated state. A default-constructed future has no associated state. future objects with associated state can only be created by use of promise ([future.promise]) or packaged_task ([future.task]) objects, and share their associated state with that promise or packaged_task. Their values or exceptions can be set by use of the promise ([future.promise]) or packaged_task ([future.task]) object that shares the same associated state.

[Note: future member functions do not synchronize with themselves or with member functions of shared_future. --end note]

The effect of calling any member function other than the destructor or the move-assignment operator on a future for which valid() == false is undefined.

  namespace std {
    template <typename R>
    class future {
    public:
      future();
      future(future &&);
      future(const future & rhs) = delete;
      ~future();
      future& operator=(const future & rhs) = delete;
      future& operator=(future&& rhs);
      see below get();
      bool is_ready() const;
      bool has_exception() const;
      bool has_value() const;
      bool valid() const;
      void wait() const;
      template <class Rep, class Period>
        bool wait_for(const chrono::duration<Rep, Period>& rel_time) const;
      template <class Clock, class Duration>
        bool wait_until(const chrono::time_point<Clock, Duration>& abs_time) const;
    };
  }

The implementation shall provide the template future and two specializations, future<R&> and future<void>. These differ only in the return type and return value of the member function get, as set out in its description, below.

future();
Effects: constructs an empty future that doesn't refer to an associated state.
Postcondition: valid() == false
future(future&& rhs);
Effects: move constructs a future object that refers to the associated state that was originally referred to by rhs (if any).
Postcondition:
Throws: nothing.
~future();
Effects:
future& operator=(future&& rhs);
Effects:
Postcondition:
R get();
R& future<R&>::get();
void future<void>::get();
Note: as described above, the template and its two required specializations differ only in the return type and return value of the member function get.
Precondition: valid() == true
Effects:
Returns:
Throws: the stored exception, if an exception was stored in the associated state.
Postcondition:: valid() == false
bool is_ready() const;
Precondition: valid() == true
Returns: true only if the associated state is ready.
bool has_exception() const;
Precondition: valid() == true
Returns: true only if the associated state is ready and the associated state contains an exception.
bool has_value() const;
Precondition: valid() == true
Returns: true only if the associated state is ready and the associated state contains a value.
bool valid() const;
Returns: true only if *this refers to an associated state.
void wait() const;
Precondition: valid() == true
Effects: blocks until the associated state is ready.
template <class Rep, class Period>
  bool wait_for(const chrono::duration<Rep, Period>& rel_time) const;
Precondition: valid() == true
Effects: blocks until the associated state is ready or until rel_time has elapsed.
Returns: true only if the associated state is ready.
template <class Clock, class Duration>
  bool wait_until(const chrono::time_point<Clock, Duration>& abs_time) const;
Precondition: valid() == true
Effects: blocks until the associated state is ready or until the current time exceeds abs_time.
Returns: true only if the associated state is ready.

Class template shared_future [future.shared_future]

The class template shared_future defines a type for asynchronous return objects which may share their associated state. A default-constructed shared_future has no associated state. A shared_future object with associated state can only be created from another shared_future with associated state or a future object with associated state. Their values or exceptions can be set by use of a promise ([future.promise]) or packaged_task ([future.task]) object that shares the same associated state.

[Note:shared_future member functions do not synchronize with themselves, but they synchronize on the shared associated state. --end note]

  namespace std {
    template <class R>
    class shared_future {
    public:
      shared_future();
      shared_future(const shared_future& rhs);
      shared_future(future<R>&&);
      shared_future(shared_future&&);
      ~shared_future();
      shared_future & operator=(const shared_future& rhs);
      shared_future & operator=(shared_future&& rhs);
      see below get() const;
      bool is_ready() const;
      bool has_exception() const;
      bool has_value() const;
      bool valid() const;
      void wait() const;
      template <class Rep, class Period>
        bool wait_for(const chrono::duration<Rep, Period>& rel_time) const;
      template <class Clock, class Duration>
        bool wait_until(const chrono::time_point<Clock, Duration>& abs_time) const;
    };
  }

The implementation shall provide the template shared_future and two specializations, shared_future<R&> and shared_future<void>. These differ only in the return type and return value of the member function get, as set out in its description, below.

shared_future();
Effects: constructs an empty shared_future that doesn't refer to an associated state.
Postcondition: valid() == false
shared_future(const shared_future& rhs);
Effects: constructs a shared_future object that refers to the same associated state as rhs (if any).
Postcondition: valid returns the same value as rhs.valid()
shared_future(future<R>&& rhs);
shared_future(shared_future&& rhs);
Effects: move constructs a shared_future object that refers to the associated state that was originally referred to by rhs (if any).
Postcondition:
Throws: nothing.
~shared_future();
Effects:
shared_future& operator=(shared_future&& rhs);
Effects:
Postcondition:
shared_future& operator=(shared_future&& rhs);
Effects:
Postcondition: valid returns the same value as rhs.valid()
const R& shared_future::get() const;
R& shared_future<R&>::get() const;
void shared_future<void>::get() const;
Note: as described above, the template and its two required specializations differ only in the return type and return value of the member function get.
Precondition: valid() == true
Effects:
Returns:
Throws: the stored exception, if an exception was stored in the associated state.
bool is_ready() const;
Precondition: valid() == true
Returns: true only if the associated state is ready.
bool has_exception() const;
Returns: true only if the associated state is ready and the associated state contains an exception.
bool has_value() const;
Returns: true only if the associated state is ready and the associated state contains a value.
bool valid() const;
Returns: true only if *this refers to an associated state.
void wait() const;
Effects: blocks until the associated state is ready.
template <class Rep, class Period>
  bool wait_for(const chrono::duration<Rep, Period>& rel_time) const;
Effects: blocks until the associated state is ready or until rel_time has elapsed.
Returns: true only if the associated state is ready.
template <class Clock, class Duration>
  bool wait_until(const chrono::time_point<Clock, Duration>& abs_time) const;
Effects: blocks until the associated state is ready or until the current time exceeds abs_time.
Returns: true only if the associated state is ready.

Class template atomic_future [future.atomic_future]

The class template atomic_future defines a type for asynchronous return objects. These objects can only be created by use of promise ([future.promise]) or packaged_task ([future.task]) objects. Their values or exceptions can be set by use of promise ([future.promise]) objects.

Contrary to future and shared_future, all member functions except constructors and the destructor are synchronization operations [intro.multithread]. Access member functions perform acquire operations on the object. All member functions calls shall be included in the order of memory_order_seq_cst operations ([atomics.order]).

  namespace std {
    template <class R>
    class atomic_future {
    public:
      atomic_future();
      atomic_future(const atomic_future& rhs);
      atomic_future(future<R>&& rhs);
      ~atomic_future();
      atomic_future & operator=(const atomic_future& rhs);
      see below get() const;
      bool is_ready() const;
      bool has_exception() const;
      bool has_value() const;
      bool valid() const;
      void wait() const;
      template <class Rep, class Period>
        bool wait_for(const chrono::duration<Rep, Period>& rel_time) const;
      template <class Clock, class Duration>
        bool wait_until(const chrono::time_point<Clock, Duration>& abs_time) const;
    };
  }

The implementation shall provide the template atomic_future and two specializations, atomic_future<R&> and atomic_future<void>. These differ only in the return type and return value of the member functions get and try_get, as set out in its description, below.

atomic_future();
Effects: constructs an empty atomic_future that doesn't refer to an associated state.
Postcondition: valid() == false
atomic_future(const atomic_future& rhs);
Effects: constructs an atomic_future object that refers to the same associated state as rhs (if any).
Postcondition: valid returns the same value as rhs.valid()
atomic_future(const future<R>&& rhs);
Effects: move constructs an atomic_future object that refers to the associated state that was originally referred to by rhs (if any).
Postcondition:
~atomic_future();
Effects:
atomic_future& operator=(const atomic_future & rhs);
Precondition: this->valid() == false
Effects: assigns the contents of rhs to *this. [Note: So *this refers to the same associated state as rhs (if any). --end note]
Synchronization: This assignment performs an acquire operation on rhs and a release operation on the left-hand side.
Postcondition: valid returns the same value as rhs.valid()
Throws: future_error with an error condition of future_already_initialized if the precondition isn't met.
const R& atomic_future::get() const;
R& atomic_future<R&>::get() const;
void atomic_future<void>::get() const;
Note: as described above, the template and its two required specializations differ only in the return type and return value of the member function get.
Precondition: valid() == true
Effects:
Returns:
Throws: the exception in the object's associated state, if an exception was stored there or future_error with an error condition of no_state if the precondition isn't met.
[Note:: Contrary to future, calling get() more than once on the same object is well defined and just produces the result again. --end note]
bool is_ready() const;
Precondition: valid() == true
Returns: true only if the associated state is ready.
bool has_exception() const;
Returns: true only if the associated state is ready and the associated state contains an exception.
bool has_value() const;
Returns: true only if the associated state is ready and the associated state contains a value.
bool valid() const;
Returns: true only if *this refers to an associated state.
void wait() const;
Effects: blocks until the associated state is ready.
template <class Rep, class Period>
  bool wait_for(const chrono::duration<Rep, Period>& rel_time) const;
Effects: blocks until the associated state is ready or until rel_time has elapsed.
Returns: true only if the associated state is ready.
template <class Clock, class Duration>
  bool wait_until(const chrono::time_point<Clock, Duration>& abs_time) const;
Effects: blocks until the associated state is ready or until the current time exceeds abs_time.
Returns: true only if the associated state is ready.

Class template packaged_task [futures.task]

A packaged_task provides a means of wrapping a function or callable object so that the return value of the function or callable object is stored in a future when it is invoked. The associated state of a packaged_task object includes storage for a copy of this associated task.

When the packaged_task is invoked, its associated task is invoked, and the result (whether normal or exceptional) stored in the associated state. Any futures that share the associated state will then be able to access the stored result.

  namespace std {
    template<class> class packaged_task;
    template<class R, class... ArgTypes>
    class packaged_task<R(ArgTypes...)> {
    public:
      typedef R result_type;
      packaged_task();
      template <class F>
        explicit packaged_task(F f);
      template <class F, class Allocator>
        explicit packaged_task(allocator_arg_t, const Allocator& a, F f);
      explicit packaged_task(R(*f)(ArgTypes...));
      template <class F>
        explicit packaged_task(F&& f);
      template <class F, class Allocator>
        explicit packaged_task(allocator_arg_t, const Allocator& a, F&& f);
        ~packaged_task();
        packaged_task(packaged_task&) = delete;
        packaged_task& operator=(packaged_task&) = delete;
        packaged_task(packaged_task&& other);
        packaged_task& operator=(packaged_task&& other);
        void swap(packaged_task& other);
        explicit operator bool() const;
        future<R> get_future();
        void operator()(ArgTypes... );
        void reset();
    };
  template <typename R, class Alloc>
    struct uses_allocator<packaged_task<R>, Alloc>;
}
template <typename R, class Alloc>
  struct uses_allocator<packaged_task<R>, Alloc>
   : true_type { };
Requires: Alloc shall be an Allocator ([allocator.requirements] 20.1.2)
packaged_task();
Effects: constructs a packaged_task object with no associated state or task.
Throws:nothing.
template <class F>
  packaged_task(F f);
template <class F, class Allocator>
  explicit packaged_task(allocator_arg_t, const Allocator& a, F f);
packaged_task(R(*f)(ArgTypes...));
template <class F>
  packaged_task(F&& f);
template <class F, class Allocator>
  explicit packaged_task(allocator_arg_t, const Allocator& a, F&& f);
Precondition: INVOKE (f, t1, t2, ..., tN, R) where t1, t2, ..., tN are values of the corresponding types in ArgTypes.... shall be a valid expression. Invoking a copy of f shall behave the same as invoking f.
Effects: constructs a new packaged_task object with a copy of f stored in the associated state as the object's associated task. The constructors that take an Allocator argument use it to allocate memory needed to store the internal data structures.
Throws: any exceptions thrown by the copy or move constructor of f, or std::bad_alloc if memory for the associated state could not be allocated.
packaged_task(packaged_task&& other);
Effects: constructs a new packaged_task object and transfers ownership of other's associated state to *this, leaving other with no associated state.
Postcondition: other has no associated state.
Throws: nothing.
packaged_task& operator=(packaged_task&& other);
Effects: As-if:
std::packaged_task<R,ArgTypes...>(other).swap(*this);
~packaged_task();
Effects: If the state associated with *this is not ready, stores an exception of type future_error with an error code of broken_promise. Any threads blocked in a member function of future, shared_future or atomic_future waiting for the state associated with *this to become ready are unblocked.
Destroys *this and releases its reference to its associated state if any. If this is the last reference to that associated state, destroys that state.
Throws: nothing.
void swap(packaged_task& other);
Effects: exchanges the associated states of *this and other.
Postcondition: *this has the same associated state (if any) as other prior to the call to swap. other has the same associated state (if any) as *this prior to the call to swap.
Throws: nothing.
explicit operator bool() const;
Returns: true if and only if *this has an associated state.
Throws: nothing.
future<R> get_future();
Returns: a future object that shares the same associated state as *this.
Throws: std::future_error if an error occurs.
Error conditions:
void operator()(ArgTypes... args);
Effects: INVOKE (f, t1, t2, ..., tN, R), where f is the task in associated state of *this and t1, t2, ..., tN are the values in args.... If the task returns normally, the return value is stored as the asynchronous result in the state associated with *this, otherwise the exception thrown by the task is stored. The state associated with *this is made ready, and any threads blocked in a member function of future, shared_future or atomic_future waiting for the state associated with *this to become ready are unblocked.
Throws: std::future_error if there is no associated state or the task has already been invoked.
Error conditions:
Synchronization: >A successful call to operator() synchronizes with ([intro.multithread]) a call to any member function of a future object, shared_future object or atomic_future object that shares the same associated state. The completion of the invocation of the associated task and the storage of the result (whether normal or exceptional) into the associated state happens-before ([intro.multithread]) that state is set to ready.
[Note: operator() synchronizes and serializes with other functions through the referred associated state. --end note]
void reset();
Effects: returns the object to a state as if a newly-constructed instance had just been assigned to *this by *this = packaged_task(std::move(f)), where f is the task stored in the associated state of *this.
[Note: This constructs a new associated state for *this. The old state is discarded, as described in the destructor for packaged_task. get_future() may now be called again for *this. -- End Note]
Throws: