p1054R0
A Unified Futures Proposal for C++

Draft Proposal,

This version:
wg21.link/P1054
Authors:
(Facebook)
(NVIDIA)
(Sandia National Labs)
(Nokia)
Audience:
SG1
Project:
ISO JTC1/SC22/WG21: Programming Language C++
Source:
github.com/executors/futures/blob/master/futures.bs

Contributors:

Marshall Cline Carter Edwards Jay Feldblum
Andrii Grynenko Jared Hoberock Hartmut Kaiser
Chris Kohlhoff Chris Mysen Eric Niebler
Sean Parent Cory Perry Felix Petriconi
Kirk Shoop Mathias Stearn

1. Introduction

This paper introduces a hierarchy of concepts for future types that are designed to:

There are five concepts introduces in this paper:

Or, described another way:

template <typename T>
struct FutureContinuation
{
  // At least one of these two overloads exists:
  auto operator()(T value);
  auto operator()(exception_arg_t, exception_ptr exception);
};

template <typename T>
struct SemiFuture
{
  template <typename Executor>
  ContinuableFuture<Executor, T> via(Executor&& exec) &&;
};

template <typename Executor, typename T>
struct ContinuableFuture
{
  template <typename RExecutor>
  ContinuableFuture<RExecutor, T> via(RExecutor&& exec) &&;

  template <typename Continuation>
  ContinuableFuture<Executor, auto> then(Continuation&& c) &&;
};

template <typename Executor, typename T>
struct SharedFuture
{
  template <typename RExecutor>
  ContinuableFuture<RExecutor, auto> via(RExecutor&& exec);

  template <typename Continuation>
  SharedFuture<Executor, auto> then(Continuation&& c);
};

template <typename T>
struct Promise
{
  void set_value(T value) &&;

  template <typename Error>
  void set_exception(Error exception) &&;
  bool valid() const;
};

In the following sections, we describe some of the key aspects of our proposed design.

1.1. The Future/Promise Execution Model

In the Concurrency TS v1, it is unspecified where a .then continuation will be run. There are a number of possible answers:

This is a source of trouble ([P0701r0] and [P0701r1]). The first two answers are undesirable, as they would require blocking, which is not ideal for an asynchronous interface. The third and fourth are likewise distasteful, as they can be vague or inefficient (respectively).

Executors, finally, give us at least a partial solution to this problem. The question changes to "where do we enqueue work into the executor"? The answer: work is always enqueued on the consumer side as if, but not necessarily, via then_execute. You can query executor properties to determine whether or not the executor’s APIs will block, which tells you whether or not continuation attachment (consumer side) or future fulfillment (producer side) potentially blocks pending execution (e.g. inline_executor semantics).

1.2. Interactions with Executors

The executors proposal defines a collection of executor types intended for use managing the execution of tasks on resources. There are three fundamental executor categories that cover directionality and control of launch:

The first two could be considered immediately launched. That is that once handed to the executor, they may start immediately, assuming the internal executor policies and resources allow it. This makes them very useful for lazy-launch scenarios.

Lazy launch scenarios are common in callback-based code, and in a wide range of future library implementations such as HPX and folly. In these designs, a callback is executed on completion of some asynchronous work, and that callback enqueues work into the executor. This means that work is enqueued only after all dependencies are satisfied.

Then-executors, on the other hand, are intended for explicitly deferred work. Work can be handed to the executor dependent on prior work, before that prior work is completed. This design is fundamentally different, but offers scope for optimization by the executor of chains of dependencies that it can batch, without running additional code on completion of each.

The current executor design is intentionally generic - it makes few requirements on the future types it can use as input dependencies for the then_execute and bulk_then_execute operations. We can assume that for a future returned by a previous call to then_execute or bulk_then_execute, the executor understands the implementation of the future can can perform whatever dependence tracking and optimization necessary. This then is an implementation detail.

However, there will also be interactions where a task to run on one executor is dependent on one produced by another. For this to be practical, we need a standardised mechanism to tie the two executors together. This amounts to a standard API for triggering deferred work.

To solve this we provide two things:

The promise is a write-only concept. This simplifies the definition and improves flexibility.

The future is not a full future in the sense of future concepts. It is merely a token that completes when the promise is satisfied. This means that it is useful only for connecting to then_execute or bulk_then_execute on the executor that provided the result.

1.3. The Exception Handling Model

Our proposal moves away from future continuations that take futures as an argument, the design used in the Concurrency TS v1. Instead, continuations take the value type of the future. This, however, requires a new exception handling model. Both the executors proposal and this paper adopt the same model, where users can provide callable objects with both a value type and an optional tag disambiguated exception handling overload.

In the example below, let f and g be ContinuableFutures and let c be a FutureContinuation.

g = f.then(c);

When f is fulfilled:

Note that if both overloads are defined, decltype(G(exception_arg, err)) shall be convertible to decltype(G(val)).

This paper defines some helper types which can be used to build callable arguments that meet the FutureContinuation arguments. For example, on_value_or_error(f, g) takes two callables (a on-value continuation and on-error continuation) and returns a single FutureContinuation object composed from the two.

1.4. The Future Cancellation Model

This paper proposes a way to hook a cancellation notifier into a future/promise pair, using cancellable_promise_contract_t. It allows for authors of future types to support cancellation if they want to, and to correctly hook into the cancellation mechanisms of other futures they interoperate with - such as inside .via, where the implementation can create a future/promise pair that will, on cancellation, invoke a function that will cancel the current future.

A sample implementation of .via could be as follows:

template <typename Executor>
auto via(Executor && ex) &&
{
    auto cancel = [this] { handle_cancellation(); };
    auto [promise, future] = execution::query(ex, cancellable_promise_contract_t{ cancel });
    then([promise = std::move(promise)](auto value) { promise.set_value(std::move(value)); });
    return future;
}

We are currently not proposing a concrete cancellation API; we plan to do that in a future revision of the paper. At this time, there is no guarantee that a future honors a cancellation notifier, and no requirements as to when it actually invokes it.

1.5. SemiFuture and .via For Composition

The SemiFuture concept and .via mechanism ([P0783r0] and [P0904r0]) gives us control of the transfer of execution ownership between executors and a way to convert between different future types.

One problem that arises in the executor model is how a future-returning interface can dictate the executor that callers attach continuations to.

SemiFuture and .via is mechanism that allows the caller to control the executor that subsequent chaining operations use. An interface may return a handle to its work, a future in the most abstract sense, that does not provide a means to chain more work. This future will complete on whatever executor that interface was using. This returned future satisfies the SemiFuture concept, and the caller is hence aware that to make use of it they must attach an executor, using .via, to transition from the interface’s executor to the caller’s executor. From that point on, the caller can use the future as necessary, safely enqueuing work onto a known executor, and protecting the interface from misuse.

Additionally, since ContinuableFuture and SharedFutures are also SemiFutures, .via provides a way to convert (if possible) from one future type (associated with a particular executor type) to another "foreign" future type (associated with another executor type).

As a simple example:

std::execution::semi_future<DataType> async_api() {
  std::execution::continuable_future<APIExecutor, SerializedDataType> =
    doAsyncWork();
  return std::move(f).then(
    [](SerializedDataType&& val){ return deserialize(val); });
}

void caller() {
  LocalExecutor e;
  auto sf = async_api();
  auto f = std::move(sf).via(e);
  std::move(f).then([](DataType&& data) {
    std::cout << "Name: " << data.name() << "\n";
  });
}

There is a strict separation of control here between caller and async_api.

1.6. Consuming Interfaces Are Rvalue Reference Qualified

In a change to the model used in std::future, where std::future::get() is a consuming operation but is l-value qualified, consuming operations in this proposal are r-value qualified.

For free functions this should be obvious. If we had a free function future_then that takes a future and returns a value, we will likely r-value qualify it:

std::future<T2> future_then(std::future<T>&& f, continuation&& c);

For consistent and safe use, the same should apply to the equivalent builtin methods.

In chaining code, this falls out cleanly:

auto f2 = do_async_thing().then([](T val){return val;}).then([](T2 val){return val;});

Occasionally we must be explicit:

auto f = do_async_thing();
auto f2 = std::move(f).then([](T val){return val;}).then([](T2 val){return val;});

but this is a minor inconvenience given that it allows the tooling to warn on use-after-move and related misuses.

1.7. Blocking Interfaces (.get and .wait)

The future concepts in this paper are intended to express the minimal requirements for executors interoperation and composition. Executors do not require a blocking interface, and futures do not require a blocking interface to compose. This paper proposes the addition of the free functions std::this_thread::future_wait and std::this_thread::future_get. These functions will block on any SemiFuture type, and are suitable for use within std::thread execution agents. Some individuals may feel that blocking interfaces are more fundamental for futures than continuation chaining interfaces - some may desire to not provide continuation chaining interfaces at all. We believe that this is a perfectly valid design; however, such non-continuable futures are outside of the scope of this work.

[P0701r0] and [P0701r1] discuss some of the challenges relating to future/promise synchronization that motivated the current design.

1.8. Future Work

There is plenty of future work on futures to be done. Here is a list of topics that are currently on our agenda:

1.9. Prior Work

This work is unification of prior papers written individually by the authors:

2. Proposed New Wording

The following wording is purely additive to the executors proposal.

2.1. The Future/Promise Execution Model

  1. A future is an object that executes, on a executor, continuations that are passed an asynchronous result as a parameter.

  1. A promise is an object that produces an asynchronous result and is associated with a future. [ Note: Although a promise is associated with one future, multiple shared future objects may refer to the same underlying shared future. - end note ]

  2. The status of a future:

  1. An asynchronous result is either a value or an exception. The result is created by the promise and accessed by the future.

  2. A future may optionally have a cancellation notifier, which is a nullary callable object.

  3. When a future is cancelled:

  1. When a continuation is attached to a future:

  1. When a promise fulfills its associated future with a value or error, if the lifetime of the future has not ended:

  1. Fulfillment of a future synchronizes with execution of continuations that were enqueued for execution by that future.

  2. The lifetime of the continuations and the asynchronous result passed to the continuations shall last until execution of the continuations completes. [ Note: The future may move (if it is a uniquely owned future) or copy (if it is a non-uniquely owned future) from the result into a new object to ensure this behavior. - End Note ] [ Note: The future may move from continuations into new object to ensure this behavior. - End Note ]

  3. The lifetime of the continuations and the asynchronous result passed to the continuations shall last until execution of the continuations completes. [ Note: The promise may move or copy (if the result is CopyConstructible) from the result into a new object to ensure this behavior. - End Note ] [ Note: The promise may move from continuations into new object to ensure this behavior. - End Note ]

  4. Upon destruction of a promise:

  1. Setting the status of a future synchronizes with operations that check the future's status.

  2. Operations that modify the set of continuations stored in a future synchronize with each other.

  3. Successful fulfillment of a future synchronizes with attachment of continuations to that future.

  4. Successful attachment of a continuation to a future synchronizes with fulfillment of the promise associated with that future.

  5. If a future has a cancellation notifier, successful fulfillment of it’s associated promise synchronizes with cancellation of the future.

2.2. FutureContinuation Requirements

namespace std::execution {

struct exception_arg_t { explicit exception_arg_t() = default; };

inline constexpr exception_arg_t exception_arg{};

template <typename F, typename T>
  struct is_future_value_continuation;

template <typename F, typename T>
  inline constexpr bool is_future_value_continuation_v
    = is_future_value_continuation<F, T>::value;

template <typename F>
  struct is_future_exception_continuation;

template <typename F>
  inline constexpr bool is_future_exception_continuation_v
    = is_future_exception_continuation<F>::value;

template <typename F, typename T>
  struct is_future_continuation;

template <typename F, typename T>
  inline constexpr bool is_future_continuation_v
    = is_future_continuation<F, T>::value;

}
  1. A future continuation is a callable object that consumes the value of a future.

  2. The struct exception_arg_t is an empty structure type used as a unique type to disambiguate FutureContinuation overloads that are called when a future holds an exception from FutureContinuation overloads that are called when a future holds a value.

  3. This sub-clause contains templates that may be used to determine at compile time whether a type meets the requirements of future continuations for a particular future value type. Each of these templates is a BinaryTypeTrait with a base characteristic of true_type if the corresponding condition is true, and false_type otherwise.

  4. A FutureContinuation type shall meet the MoveConstructible requirements and the requirements described in the Tables below.

Type Property Queries

Template Condition Preconditions
template <typename F, typename T>
struct is_future_value_continuation;
is_move_constructible_v<F> && is_invocable_v<F, T> T is a complete type.
template <typename F>
struct is_future_exception_continuation;
is_move_constructible_v<F> && is_invocable_v<F, exception_arg_t, exception_ptr> T is a complete type.
template <typename F, typename T>
struct is_future_continuation;
is_future_value_continuation_v<F, T> || is_future_exception_continuation_v<F> T is a complete type.

2.3. Promise Requirements

#. A Promise type for value type T and error type E shall meet the MoveConstructible requirements, the MoveAssignable requirements, and the requirements described in the Tables below.

Descriptive Variable Definitions

Variable Definition
T Either:
  • Any (possibly cv-qualified) object type that is not an array, or

  • (possibly cv-qualified) void.

t a value of a type contextually convertible to T
e a value of type contextually convertible to exception_ptr
P<T> A promise type for value type T
p An rvalue of type P<T>


Expression Return Type Operational semantics
promise_value_t<P<T>> T
p.set_value(t) void
  • Requires: !is_void_v<T>

  • Effects: Completes the promise and associated future with t.

p.set_value() void
  • Requires: is_void_v<T>

  • Effects: Completes the promise and associated future.

p.set_exception(e) void Completes the promise and associated future with the error e.
p.valid() Contextually convertible to bool. true if the promise has an associated future that is incomplete, false otherwise.

2.3.1. Promise Contract Executor Properties

template <class T>
struct promise_contract_t
{
  static constexpr bool is_requirable = false;
  static constexpr bool is_preferable = false;

  using polymorphic_query_result_type
    = std::pair<std::execution::promise<T>, std::execution::semi_future<T>>;
};

template <typename T>
inline constexpr promise_contract = promise_contract_t<T>{};

The promise_contract_t property can be used only with query.

The result of a query of the promise_contract_t property applied to a ThenExecutor or BulkThenExecutor is a std::pair consisting of a Promise and an implementation-defined token type that will be interpreted as a valid input future by calls to then_execute or bulk_then_execute and that is satisfied by calling set_value or set_exception on the promise.

The value returned from execution::query(e, promise_contract_t<T>), where e is an executor and T is a type, should be unique for any given call. When e is a ThenExecutor or BulkThenExecutor the result of the query is a std::pair where first value is an instance of a type matching the Promise requirements and the second is a token type that e will interpret as a valid future parameter to calls to then_execute or bulk_then_execute.

template <T, C>
struct cancellable_promise_contract_t
{
  static constexpr bool is_requirable = false;
  static constexpr bool is_preferable = false;

  using polymorphic_query_result_type
    = std::pair<std::execution::promise<T>, std::execution::semi_future<T>>;

  template<class Executor>
  static constexpr decltype(auto) static_query_v
    = Executor::query(promise_contract_t());

  template<class Executor>
    friend std::pair<std::execution::promise<T>, std::execution::semi_future<T>>
      query(const Executor& ex, const cancellable_promise_contract_t&);

  CancellationNotifier cancellation_notifier;
};

The cancellable_promise_contract_t property can be used only with query. cancellable_promise_contract_t differs from promise_contract_t in that the query carries a cancellation callback, cancellation_notifier, that will be called as std::invoke(cancellation_notifier) by the ThenExecutor on cancellation of the task dependent on the future resulting from the cancellable_promise_contract_t query.

The result of a query of the cancellable_promise_contract_t property applied to a ThenExecutor or BulkThenExecutor is a std::pair consisting of a Promise and an implementation-defined token type that will be interpreted as a valid input future by calls to then_execute or bulk_then_execute, that is satisfied by calling set_value or set_exception on the promise and that supports cancellation by the executor.

The value returned from execution::query(e, cancellable_promise_contract_t<T>{cancellation_notifier}), where e is an executor and T is a type, should be unique for any given call. When e is a ThenExecutor or BulkThenExecutor the result of the query is a std::pair where first value is an instance of a type matching the Promise requirements and the second is a token type that e will interpret as a valid future parameter to calls to then_execute or bulk_then_execute.

template<class Executor>
  friend std::pair<std::execution::promise<T>, std::execution::semi_future<T>>
    query(const Executor& ex, const cancellable_promise_contract_t&);

2.4. SemiFuture Requirements

  1. A semi future is an object that can be bound to an executor to produce a future.

  2. A SemiFuture type for type T shall meet the MoveConstructible requirements, the MoveAssignable requirements, and the requirements described in the Tables below.

Descriptive Variable Definitions

Variable Definition
T Either:
  • Any (possibly cv-qualified) object type that is not an array, or

  • (possibly cv-qualified) void.

SF<T> A SemiFuture type for value type T.
sf An rvalue of type SF<T>.
E An executor type, either:
  • A ThenExecutor such that execution::can_query_v<E, promise_contract_t<T>> == true, or

  • A OneWayExecutor otherwise.

e A value of type E.
CF<T, E> A ContinuableFuture type for value type T and executor type E, either:
  • decltype(execution::query(e, promise_contract_t<T>()).second) if E is a ThenExecutor and execution::can_query_v<E, promise_contract_t<T>> == true, or

  • continuable_future<T, E> otherwise.

SemiFuture Requirements

Expression Return Type Operational Semantics
future_value_t<SF<T>> T
future_exception_t<SF<T>> Implicitly convertible to exception_ptr.
sf.via(e) implementation-defined Returns: A ContinuableFuture for value type T that is bound to the executor e and will be made ready with the value or exception of sf when sf is made ready.

Throws: If !execution::query(e, promise_contract_t<T>).second, throws any exception thrown by !execution::query(e, oneway_t).

2.5. ContinuableFuture Requirements

  1. A continuable future is a future that is bound to an executor and can have continuations attached to it.

  2. A ContinuableFuture shall meet the SemiFuture requirements and the requirements described in the Tables below.

Descriptive Variable Definitions

Variable Definition
E An executor type.
e A value of type E.
T Either:
  • Any (possibly cv-qualified) object type that is not an array, or

  • (possibly cv-qualified) void.

CF<T, E> A ContinuableFuture type for value type T and executor type E.
cf A value of type CF<T, E>.
rcf An rvalue of type CF<T, E>.
val The value contained within the successfully completed future rcf.
ex The exception contained within the exceptionally completed future rcf.
G Any type such that is_future_continuation_v<G, T> == true.
g An object of type G.
R Either:
  • If is_same_v<remove_cv_t<T>, void> == true and invoke(g) is well formed, decltype(g()).

  • If is_same_v<remove_cv_t<T>, void> != true and invoke(g, val) is well formed, decltype(g(val)).

  • Otherwise, T.

SF<T> A SemiFuture type for value type T.
NORMAL Either:
  • If is_same_v<remove_cv_t<T>, void> == true, DECAY_COPY(std::forward<G>(g))(), and

  • Otherwise, DECAY_COPY(std::forward<G>(g))(std::move(val)).

EXCEPTIONAL The expression DECAY_COPY(std::forward<G>(g))(exception_arg, std::move(ex)).

ContinuableFuture Requirements

Expression Return Type Operational Semantics
cf.get_executor() E Returns: The executor that the future is bound to.
rcf.then(g) CF<E, R> Returns: A ContinuableFuture that is bound to the executor e and that wraps the type returned by execution of either the value or exception operations implemented in the continuation.

Effects: When rcf becomes nonexceptionally ready, and if NORMAL is a well-formed expression, creates an execution agent which invokes NORMAL at most once, with the call to DECAY_COPY being evaluated in the thread that called .then.

Otherwise, when rcf becomes exceptionally ready, if EXCEPTIONAL is a well-formed expression, creates an execution agent which invokes EXCEPTIONAL at most once, with the call to DECAY_COPY being evaluated in the thread that called .then.

If NORMAL and EXCEPTIONAL are both well-formed expressions, decltype(EXCEPTIONAL) shall be convertible to R.

If NORMAL is not a well-formed expression and EXCEPTIONAL is a well-formed expression, decltype(EXCEPTIONAL) shall be convertible to decltype(val).

If neither NORMAL nor EXCEPTIONAL are well-formed expressions, the invocation of .then shall be ill-formed.

May block pending completion of NORMAL or EXCEPTIONAL.

The invocation of .then synchronizes with (C++Std [intro.multithread]) the invocation of g.

Fulfills the ContinuableFuture with the result of the NORMAL or EXCEPTIONAL expression, or any exception thrown by either. Otherwise, fulfills the ContinuableFuture with either val or e.

Synchronization: The destruction of the continuation that generates rcf's value synchronizes with the invocation of g and with the destruction of g.

2.6. SharedFuture Requirements

  1. A shared future is a non-uniquely owned future that is copyable, is bound to an executor and that allows one or more continuation to be attached to it.

  2. A SharedFuture shall meet the ContinuableFuture requirements, the CopyConstructible requirements, the CopyAssignable requirements and the requirements described in the Tables below.

Descriptive Variable Definitions

Variable Definition
E An executor type.
e A value of type E.
T Any (possibly cv-qualified) object type that is not an array.
CF<T, E> A ContinuableFuture type for executor type E and value type T.
SHF<E, T> A SharedFuture type for executor type E and value type T.
shf A value of type SHF<E, T>.
NORMAL The expression DECAY_COPY(std::forward<G>(g))(val) if T is non-void and DECAY_COPY(std::forward<G>(g))() if T is void.
EXCEPTIONAL The expression DECAY_COPY(std::forward<G>(g))(exception_arg, ex),

SharedFuture Requirements

Expression Return Type Operational Semantics
shf.then(g) If T is non-void and INVOKE(declval<G>(), declval<T>()) or if T is void and INVOKE(declval<G>()) is well-formed:

CF<E, decltype(INVOKE(declval<G>(), declval<T>()))>

Otherwise:

CF<T, E>

Returns: A ContinuableFuture that is bound to the executor e and that wraps the type returned by execution of either the value or exception operations implemented in the continuation.

Effects: When shf becomes nonexceptionally ready, and if NORMAL is a well-formed expression, creates an execution agent which invokes NORMAL at most once, with the call to DECAY_COPY being evaluated in the thread that called .then.

Otherwise, when shf becomes exceptionally ready, if EXCEPTIONAL is a well-formed expression, creates an execution agent which invokes EXCEPTIONAL at most once, with the call to DECAY_COPY being evaluated in the thread that called .then.

If NORMAL and EXCEPTIONAL are both well-formed expressions, decltype(EXCEPTIONAL) shall be convertible to R.

If NORMAL is not a well-formed expression and EXCEPTIONAL is a well-formed expression, decltype(EXCEPTIONAL) shall be convertible to decltype(val).

If neither NORMAL nor EXCEPTIONAL are well-formed expressions, the invocation of .then shall be ill-formed.

May block pending completion of NORMAL or EXCEPTIONAL.

The invocation of .then synchronizes with (C++Std [intro.multithread]) the invocation of g.

Fulfills the ContinuableFuture with the result of the NORMAL or EXCEPTIONAL expression, or any exception thrown by either. Otherwise, fulfills the ContinuableFuture with either val or e.

Postconditions: Has no observable affect on sfh.

shf.via(e) Implementation-defined Returns: A ContinuableFuture for type T that is bound to the executor e.

Effect: Returns an implementation-defined ContinuableFuture onto which continuations can be attached that will run on e.

Success: Succeeds if:

  • e is a ThenExecutor where make_promise_contract(e) is well-formed.

  • e is a OnewayExecutor or is convertible to a OnewayExecutor.

Fails at compile-time otherwise.

Postconditions: Has no observable affect on sfh.

2.7. std::execution::promise

template <class T>
class promise {
public:
    using value_type = T;

    promise() noexcept;
    promise(promise&&) noexcept;
    template <typename Promise>
    explicit promise(Promise&& p);

    void set_value(/* see below */) &&;
    
    template <typename Error>
    void set_exception(Error&& err) &&;

    bool valid() const noexcept;
    explicit operator bool() const noexcept;
};

A promise refers to a promise and is associated with a future, either through type-erasure or through construction of an underlying promise with an overload of make_promise_contract().


promise() noexcept;


promise(promise&& rhs) noexcept;


template <class Promise>
promise(Promise&& rhs) noexcept;


void promise::set_value(const T& val) &&;
void promise::set_value(T&& val) &&;
void promise<void>::set_value() &&;


template <typename Error>
void set_exception(Error&& err) &&;


bool valid() const noexcept;
explicit operator bool() const noexcept;

2.8. std::execution::semi_future

template<class T>
class semi_future {
public:
    using value_type = T;

    semi_future(semi_future&&) = default;
    semi_future(const semi_future&) = delete;

    semi_future& operator=(const semi_future&) = delete;
    semi_future& operator=(semi_future&&) = default;

    template<class E>
    explicit semi_future(continuable_future<T, E>&&);

    template<class E>
    explicit semi_future(shared_future<T, E>&&);

    template<class EI>
    continuable_future<T, EI> via(EI) &&;
};


continuable_future(semi_future&& rhs);


semi_future(const semi_future& rhs);


template<class E>
semi_future(continuable_future<T, E>&& rhs);


template<class E>
explicit semi_future(shared_future<T, E>&& rhs);

template<class E>
explicit semi_future(const shared_future<T, E>& rhs);


continuable_future<T, EI> via(EI ex) &&;


bool valid() const noexcept;

2.9. std::execution::continuable_future

template<class T, class E>
class continuable_future {
public:
    using value_type = T;
    using executor_type = Ex;
    using semi_future_type = semi_future<T>;

    continuable_future(const continuable_future&) = delete;
    continuable_future(continuable_future&&) = default;

    continuable_future& operator=(const continuable_future&) = delete;
    continuable_future& operator=(continuable_future&&) = default;

    template<class E>
    explicit continuable_future(shared_future<T, E>&&);
    template<class E>
    explicit continuable_future(const shared_future<T, E>&);

    template<class ReturnFuture, class F>
    ReturnFuture then(FutureContinuation&&) &&;

    template<class EI>
    continuable_future<T, EI> via(EI) &&;

    E get_executor() const;
    semi_future<T> semi() &&;
    shared_future<T, E> share() &&;

    bool valid() const
}
};


continuable_future(continuable_future&& rhs);


continuable_future(const continuable_future& rhs);


template<class E>
explicit continuable_future(shared_future<T, E>&& rhs);


template<class E>
explicit continuable_future(const shared_future<T, E>& rhs);


template<class ReturnFuture, class F>
ReturnFuture then(F&&) &&;


continuable_future<T, EI> via(EI ex) &&;


E get_executor() const;


semi_future<T> semi() &&;


bool valid() const noexcept;


shared_future<T, E> share() &&;

2.10. std::execution::shared_future

namespace std::execution {
  template<class T, class E>
  class shared_future {
  public:
      using value_type = T;
      using executor_type = Ex;
      using semi_future_type = semi_future<T>;

      shared_future(const shared_future&) = default;
      shared_future(shared_future&&) = default;

      shared_future& operator=(const shared_future&) = default
      shared_future& operator=(shared_future&&) = default;

      template<class E>
      explicit shared_future(continuable_future<T, E>&&);

      template<class ReturnFuture, class F>
      ReturnFuture then(FutureContinuation&&);

      template<class EI>
      shared_future<T, EI> via(EI);

      E get_executor() const;
      semi_future<T> semi();

      bool valid() const;
  }
  };
}


shared_future(shared_future&& rhs);


shared_future(const shared_future& rhs);


explicit shared_future(continuable_future&& rhs);


template<class ReturnFuture, class F>
ReturnFuture then(F&&);


shared_future<T, EI> via(EI ex);


E get_executor() const;


semi_future<T> semi();


bool valid() const noexcept;

2.11. std::execution::make_promise_contract

template <class T, class Executor>
/* see below */
make_promise_contract(const Executor& ex)
  requires execution::is_then_executor_v<Executor> && execution::can_query_v<Executor, promise_contract_t<T>>


template <class T, class Executor>
pair<promise<T>, continuable_future<T, decay_t<Executor>>
make_promise_contract(const Executor& ex)
  requires execution::is_one_way_executor_v<Executor>


template <class T>
pair<promise<T>, semi_future<T>>
make_promise_contract()

2.12. Generic Future Blocking Functions

In [thread.syn] and [thread.thread.this] add:

namespace std::this_thread {

  template<class Future>
    void future_wait(Future& f) noexcept;

  template<class Future>
    future_value_t<decay_t<Future>> future_get(Future&& f);

}

In [thread.thread.this] add:

template<class Future>
  void future_wait(Future& f) noexcept;

2.13. FutureContinuation Helper Functions

namespace std::execution {

template <class F>
  /* see below */ on_value(F&& f);

template<class F>
  /* see below */ on_error(F&& f);

template<class F, class G>
  /* see below */ on_value_or_error(F&& f, G&& g);

}
template<class F>
  /* see below */ on_value(F&& f);

Requires:

Returns: A FutureContinuation object, fc, of implementation-defined type such that:

Requires:

Returns: A FutureContinuation object, fc, of implementation-defined type such that:

Requires:

Returns: A FutureContinuation object, fc, of implementation-defined type such that:

2.14. Proposed Modifications to Executors

2.14.1. Future Requirements

Remove this section.

2.14.2. TwoWayExecutor Requirements

In the Return Type column:

Replace:

A type that satisfies the Future requirements for the value type R.

With:

A type that satisfies the ContinuableFuture requirements for the value type R.

In the Operational Semantics column:

Replace:

in the associated shared state of the resulting Future.

With:

in the resulting ContinuableFuture.

2.14.3. ThenExecutor Requirements

In the type requirements list:

Replace:

With:

In the Return Type column:

Replace:

A type that satisfies the Future requirements for the value type R.

With:

A type that satisfies the ContinuableFuture requirements for the value type R.

In the Operational Semantics column:

Replace:

in the associated shared state of the resulting Future.

With:

in the resulting ContinuableFuture.

2.14.4. BulkTwoWayExecutor Requirements

In the Return Type column:

Replace:

A type that satisfies the Future requirements for the value type R.

With:

A type that satisfies the ContinuableFuture requirements for the value type R.

In the Operational Semantics column:

Replace:

in the associated shared state of the resulting Future.

With:

in the resulting ContinuableFuture.

2.14.5. BulkThenExecutor Requirements

In the type requirements list:

Replace:

With:

In the Return Type column:

Replace:

A type that satisfies the Future requirements for the value type R.

With:

A type that satisfies the ContinuableFuture requirements for the value type R

In the Operational Semantics column:

Replace:

in the associated shared state of the resulting Future.

With:

in the resulting ContinuableFuture.

2.14.6. twoway_t Customization Points

Replace:

it is std::experimental::future<T>

With:

it is a execution::continuable_future<T, E1>

2.14.7. single_t Customization Points

Replace:

it is std::experimental::future<T>

With:

it is execution::continuable_future<T, E1>

2.14.8. Properties To Indicate If Blocking And Directionality May Be Adapted

Remove twoway_t from the Requirements column of the Table.

2.14.9. Class Template executor

Replace:

template<class Function>
  std::experimental::future<result_of_t<decay_t<Function>()>>
    twoway_execute(Function&& f) const

With:

template<class Function>
  execution::semi_future<result_of_t<decay_t<Function>()>>
    twoway_execute(Function&& f) const

Replace:

template<class Function, class ResultFactory, class SharedFactory>
  std::experimental::future<result_of_t<decay_t<ResultFactory>()>>
    bulk_twoway_execute(Function&& f, size_t n, ResultFactory&& rf, SharedFactory&& sf) const;

With:

template<class Function, class ResultFactory, class SharedFactory>
  execution::semi_future<result_of_t<decay_t<ResultFactory>()>>
    bulk_twoway_execute(Function&& f, size_t n, ResultFactory&& rf, SharedFactory&& sf) const;

2.14.10. executor Operations

Replace:

template<class Function>
  std::experimental::future<result_of_t<decay_t<Function>()>>
    twoway_execute(Function&& f) const

With:

template<class Function>
  /* implementation-defined future type */
    twoway_execute(Function&& f) const

Replace:

Returns: A future, whose shared state is made ready when the future returned by e.twoway_execute(f2) is made ready, containing the result of f1() or any exception thrown by f1(). [ Note: e2.twoway_execute(f2) may return any future type that satisfies the Future requirements, and not necessarily One possible implementation approach is for the polymorphic wrapper to attach a continuation to the inner future via that object’s then() member function. When invoked, this continuation stores the result in the outer future’s associated shared state and makes that shared state ready. — end note ]

With:

Returns: A value whose type satisfies the ContinuableFuture requirements. The returned future is fulfilled when f1() completes execution, with the result of f1() (if decltype(f1()) is void), valueless completion (if decltype(f1()) is void), or any exception thrown by f1().

Replace:

template<class Function, class ResultFactory, class SharedFactory>
  std::experimental::future<result_of_t<decay_t<ResultFactory>()>>
    void bulk_twoway_execute(Function&& f, size_t n, ResultFactory&& rf, SharedFactory&& sf) const;

With:

template<class Function, class ResultFactory, class SharedFactory>
  /* implementation-defined future type */
     bulk_twoway_execute(Function&& f, size_t n, ResultFactory&& rf, SharedFactory&& sf) const;

Replace:

Returns: A future, whose shared state is made ready when the future returned by e.bulk_twoway_execute(f2, n, rf2, sf2) is made ready, containing the result in r1 (if decltype(rf1()) is non-void) or any exception thrown by an invocation f1. [ Note: e.bulk_twoway_execute(f2) may return any future type that satisfies the Future requirements, and not necessarily std::experimental::future. One possible implementation approach is for the polymorphic wrapper to attach a continuation to the inner future via that object’s then() member function. When invoked, this continuation stores the result in the outer future’s associated shared state and makes that shared state ready. — end note ]

With:

Returns:

A value whose type satisfies the ContinuableFuture requirements. The returned future is fulfilled when f1() completes execution, with the result in r1 (if decltype(rf1()) is void), valueless completion (if decltype(rf1()) is void), or any exception thrown by an invocation f1.

2.14.11. static_thread_pool Executor Type

Replace:

template<class Function>
  std::experimental::future<result_of_t<decay_t<Function>()>>
    twoway_execute(Function&& f) const

template<class Function, class Future>
  std::experimental::future<result_of_t<decay_t<Function>(decay_t<Future>)>>
    then_execute(Function&& f, Future&& pred) const;

template<class Function, class SharedFactory>
  void bulk_execute(Function&& f, size_t n, SharedFactory&& sf) const;

template<class Function, class ResultFactory, class SharedFactory>
  std::experimental::future<result_of_t<decay_t<ResultFactory>()>>
    void bulk_twoway_execute(Function&& f, size_t n, ResultFactory&& rf, SharedFactory&& sf) const;

With:

template<class Function>
  std::execution::continuable_future<result_of_t<decay_t<Function>()>, C>
    twoway_execute(Function&& f) const

template<class Function, class Future>
  execution::continuable_future<result_of_t<decay_t<Function>(decay_t<Future>)>, C>
    then_execute(Function&& f, Future&& pred) const;

template<class Function, class SharedFactory>
  void bulk_execute(Function&& f, size_t n, SharedFactory&& sf) const;

template<class Function, class ResultFactory, class SharedFactory>
  execution::continuable_future<result_of_t<decay_t<ResultFactory>()>>, C>
    void bulk_twoway_execute(Function&& f, size_t n, ResultFactory&& rf, SharedFactory&& sf) const

Replace the same instances in the documentation section below the main code block.

Index

Terms defined by this specification

References

Informative References

[P0701r0]
Bryce Adelstein Lelbach. Back to the std2::future. URL: https://wg21.link/p0701r0
[P0701r1]
Bryce Adelstein Lelbach. Back to the std2::future. URL: https://wg21.link/p0701r1
[P0783r0]
Lee Howes, Andrii Grynenko, Jay Feldblum. Continuations without overcomplicating the future. URL: https://wg21.link/p0783r0
[P0904r0]
Lee Howes, Andrii Grynenko, Jay Feldblum. A strawman Future API. URL: https://wg21.link/p0904r0