| Document Number: | |
|---|---|
| Date: | |
| Revises: | |
| Editor: | Microsoft Corp. |
Note: this is an early draft. It’s known to be incomplet and incorrekt, and it has lots of bad formatting.
This technical specification describes a number of concurrency extensions to
the C++ Standard Library (
This technical specification is non-normative. Some of the library components in this technical specification may be considered for standardization in a future version of C++, but they are not currently part of any C++ standard. Some of the components in this technical specification may never be standardized, and others may be standardized in a substantially changed form.
The goal of this technical `specification` is to build more widespread existing practice for an expanded C++ standard library. It gives advice on extensions to those vendors who wish to provide them.
The following referenced document is indispensable for the application of this document. For dated references, only the edition cited applies. For undated references, the latest edition of the referenced document (including any amendments) applies.
ISO/IEC 14882:— is herein called the C++ Standard. References to clauses within the C++ Standard are written as "C++14 §3.2". The library described in ISO/IEC 14882:— clauses 17–30 is herein called the C++ Standard Library.
Some of the extensions described in this Technical Specification represent
types and functions that are currently not part of the C++ Standards Library,
and because these extensions are experimental, they should not be declared
directly within namespace std. Instead, such extensions are
declared in namespace std::experimental.
std.
— end note ]
Unless otherwise specified, references to such entities described in this
Technical Specification are assumed to be qualified with
std::experimental, and references to entities described in the C++
Standard Library are assumed to be qualified with std::.
std::future<T> and Related APIs
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.
future
To the class declaration found in
bool is_ready() const;
future(future<future<R>>&& rhs) noexcept;
template<typename F>
see below then(F&& func);
template<typename F>
see below then(executor &ex, F&& func);
template<typename F>
see below then(launch policy, F&& func);
In
future(future<future<R>>&& rhs) noexcept;
future object by moving the instance referred to by
rhs and unwrapping the inner future.valid() returns the same value as rhs.valid() prior to the
constructor invocation.rhs.valid() == false.
After
template<typename F>
see below then(F&& func);
template<typename F>
see below then(executor &ex, F&& func);
template<typename F>
see below then(launch policy, F&& func);
future object as a parameter. executor as the first parameter and a callable object
as the second parameter.INVOKE(DECAY_COPY (std::forward<F>(func))) is called when the object's shared state is ready (has a value or exception stored).executorexecutor orexecutorfuture. Any exception propagated from the execution of
the continuation is stored as the exceptional result in the shared state of the resulting future.
std::promise or with a packaged_task (has
no associated launch policy), the continuation behaves the same as the launch::async | launch::deferred and the
same argument for func.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 n) { return 2; });
f2.wait(); // execution of f1 starts here, followed by f2
— end example ]
then depends on the return type of the closure
func as defined below:
result_of_t<decay_t<F>()>
is future<R>, the function returns future<R>.
future<result_of_t<decay_t<F>()>>.
then taking a closure returning a
future<R> would have been future<future<R>>.
This rule avoids such nested future objects.
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;
});
— end example ]
future object is moved to the parameter of the continuation function.valid() == false on original future object immediately after it returns.bool is_ready() const;
true if the shared state is ready, false if it isn't.shared_futurebool is_ready() const; template<typename F> see below then(F&& func);template<typename F> see below then(executor &ex, F&& func);template<typename F> see below then(launch policy, F&& func);
template<typename F>
see below shared_future::then(F&& func);
template<typename F>
see below shared_future::then(executor &ex, F&& func);
template<typename F>
see below shared_future::then(launch policy, F&& func);
shared_future object as a
parameter. executor as the first parameter and a
callable object as the second parameter.INVOKE(DECAY_COPY (std::forward<F>(func))) is called when the object's shared state is ready (has a value or exception stored).future. Any exception propagated from the execution of
the continuation is stored as the exceptional result in the shared state of the resulting future.
std::promise (has no associated launch
policy), the continuation behaves the same as the launch::async | launch::deferred and the same argument for func.launch::deferred, then it is filled by
calling wait() or get() on the resulting shared_future.
future. See example in then depends on the return type of the closure
func as defined below:
result_of_t<decay_t<F>()>
is future<R>, the function returns future<R>.
future<result_of_t<decay_t<F>()>>.
future. See the notes on future::then return type in shared_future passed to the continuation function is
a copy of the original shared_future.
valid() == true on the original shared_future object.
bool is_ready() const;
true if the shared state is ready, false if it isn't.when_all
A new section 30.6.10 shall be inserted at the end of
template
see below when_all(InputIterator first, InputIterator last);
template <typename... T>
see below when_all(T&&... futures);
T is of type future<R> or
shared_future<R>.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.when_all where InputIterator first
equals last, returns a future with an empty vector that is immediately
ready.when_any with no arguments returns a
future<tuple<>> that is immediately ready.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.when_all will not throw an exception, but the
futures held in the output collection may.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
futures 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 futures 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.
may be void.future<T>s valid() == false.shared_future<T> valid() == true.when_any
A new section 30.6.11 shall be inserted at the end of
template <class InputIterator>
see below when_any(InputIterator first, InputIterator last);
template <typename... T>
see below when_any(T&&... futures);
T is of type future<R> or shared_future<R>.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.when_any where InputIterator first
equals last, returns a future with an empty vector that is immediately
ready.when_any with no arguments returns a
future<tuple<>> that is immediately ready.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.future returned by when_any will not throw an exception, but the
futures held in the output collection may.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 futures 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 futures 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.future<T>s valid() == false.shared_future<T> valid() == true.when_any_back
A new section 30.6.12 shall be inserted at the end of
template <class InputIterator>
see below when_any_back(InputIterator first, InputIterator last);
InputIterator's value type shall be convertible to future<R>
or shared_future<R>. All R types must be the same.
when_any_back takes a pair of InputIterators.when_any_back where InputIterator first equals
last, returns a future with an empty vector that is immediately ready.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.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.future returned by when_any_back will not throw an exception, but
the futures held in the output collection may.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.future<T>s valid() == false.shared_future valid() == true .make_ready_future
A new section 30.6.13 shall be inserted at the end of
template <typename T>
future<decay_t<T>> make_ready_future(T&& value);
future<void> make_ready_future();
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. future<decay_t<T>>, valid() == true.future<decay_t<T>>, is_ready() == true.async
Change
The function template async provides a mechanism to launch a function
potentially in a new thread and provides the result of the function in a future
object with which it shares a shared state.
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);Changetemplate<class F, class... Args> future<result_of_t<decay_t<F>(decay_t<Args>...)>> async(executor& ex, F&& f, Args&&... args);
launch::async | launch::deferred and the
same arguments for F and Args. The second and third functions createpolicy & launch::async is non-zero — calls
INVOKE (DECAY_COPY (std::forward<F>(f)), DECAY_COPY (std::forward<Args>(args))...)
(20.8.2, 30.3.1.2) as if in a new thread of execution represented by a thread object
with the calls to DECAY_COPY () being evaluated in the thread that called
async. Any return value is stored as the result in the shared state. Any
exception propagated from the execution of
INVOKE (DECAY_COPY (std::forward<F>(f)), DECAY_COPY (std::forward<Args>(args))...)
is stored as the exceptional result in the shared state. The thread object is stored in the
shared state and affects the behavior of any asynchronous return objects that
reference that state.policy & launch::deferred is non-zero — Stores DECAY_COPY(std::forward<F>(f))
and DECAY_COPY (std::forward<Args>(args))... in the
shared state. These copies of f and args constitute a deferred function.
Invocation of the deferred function evaluates
INVOKE std::move(g), std::move(xyz)) where g is the stored value of
DECAY_COPY (std::forward<F>(f)) and xyz is the stored copy of
DECAY_COPY (std::forward<Args>(args)).... The shared state is not made ready until the
function has completed. The first call to a non-timed waiting function (30.6.4)
on an asynchronous return object referring to this shared state shall invoke
the deferred function in the thread that called the waiting function. Once
evaluation of INVOKE (std::move(g), std::move(xyz)) begins, the function is no
longer considered deferred. launch::async | launch::deferred, implementations should defer invocation or the selection of
the policy when no more concurrency can be effectively exploited.
— end note ]
Theexecutor::add()function is given afunction<void()>which callsINVOKE (DECAY_COPY (std::forward<F>(f)) DECAY_COPY (std::forward<Args>(args))...). The implementation of the executor is decided by the programmer.