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).executor
executor
orexecutor
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
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— end example ]n) { return 2; }); f2.wait(); // execution of f1 starts here, followed by f2
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_future
bool 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.