Technical Specification for C++ Extensions for Concurrency, Working Draft

Document Number: | N4107
Date: | 2014-07-07
Revises: | N3970
Editor: | Artur Laksberg Microsoft Corp. arturl@microsoft.com

# Working Draft, Technical Specification for C++ Extensions for Concurrency

Note: this is an early draft. It’s known to be incomplet and incorrekt, and it has lots of bad formatting.

1

# General

[general]
1.1

# Scope

[general.scope]

This technical specification describes a number of concurrency extensions to the C++ Standard Library (1.2). These extensions are classes and functions that are likely to be used widely within a program and/or on the interface boundaries between libraries written by different organizations.

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.

1.2

# Normative references

[general.references]

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:—1
  1) To be published. Section references are relative to N3797.
  , Programming Languages — C++
- RFC 2781, UTF-16, an encoding of ISO 10646

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.

1.3

# Namespaces, headers, and modifications to standard classes

[general.namespaces]

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`.

[ Note: Once standardized, these components are expected to be promoted to namespace `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::`.

2

# Improvements to `std::future<T>` and Related APIs

[future]
2.1

# General

[futures.general]

The extensions proposed here are an evolution of the functionality of `std::future` and `std::shared_future`. The extensions enable wait free composition of asynchronous operations.

2.2

# Changes to class template `future`

[futures.unique_future]

To the class declaration found in C++14 §30.6.6 paragraph 3, add the following to the public functions:

    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 C++14 §30.6.6 between paragraphs 8 and 9, add the following:

    future(future<future<R>>&& rhs) noexcept;

Effects:
  Constructs a `future` object by moving the instance referred to by `rhs` and unwrapping the inner future.
Postconditions:
  - `valid()` returns the same value as `rhs.valid()` prior to the constructor invocation.
  - `rhs.valid() == false`.

After C++14 §30.6.6 paragraph 26, add the following:

    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);

Notes:
  The threetwo functions differ only by input parameters. The first only takes a callable object which accepts a `future` object as a parameter. The second function takes an `executor` as the first parameter and a callable object as the second parameter. The thirdsecond function takes a launch policy as the first parameter and a callable object as the second parameter.
Effects:

- The continuation `INVOKE(DECAY_COPY (std::forward<F>(func)))` is called when the object's shared state is ready (has a value or exception stored).
- The continuation launches according to the specified launch policy or `executor`.
- When the `executor` or launch policy is not provided the continuation inherits the parent's launch policy or `executor`.
- Any value returned from the continuation is stored as the result in the shared state of the resulting `future`. Any exception propagated from the execution of the continuation is stored as the exceptional result in the shared state of the resulting `future`.
- If the parent was created with `std::promise` or with a `packaged_task` (has no associated launch policy), the continuation behaves the same as the thirdsecond overload with a policy argument of `launch::async | launch::deferred` and the same argument for `func`.
- If the parent has a policy of `launch::deferred`, then it is filled by calling `wait()` or `get()` on the resulting `future`. [ Example:

      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 ]

Returns:
  The return type of `then` depends on the return type of the closure `func` as defined below:
  - When `result_of_t<decay_t<F>()>` is `future<R>`, the function returns `future<R>`.
  - Otherwise, the function returns `future<result_of_t<decay_t<F>()>>`.
Notes:
  The first rule above is called the implicit unwrapping. Without this rule, the return type of `then` taking a closure returning a `future<R>` would have been `future<future<R>>`. This rule avoids such nested `future` objects. [ Example: The type of `f2` below is `future<int>` and not `future<future<int>>`:

      future<int> f1 = g();
      future<int> f2 = f1.then([](future<int> f) {
                          future<int> f3 = h();
                          return f3;
                       });

  — end example ]
Postconditions:
  - The `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;

Returns:
  `true` if the shared state is ready, `false` if it isn't.

2.3

# Changes to class template `shared_future`

[futures.shared_future]

To the class declaration found in C++14 §30.6.7 paragraph 3, add the following to the public functions:

    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);

After C++14 §30.6.7 paragraph 28, add the following:

    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);

Notes:
  The threetwo functions differ only by input parameters. The first only takes a callable object which accepts a `shared_future` object as a parameter. The second function takes an `executor` as the first parameter and a callable object as the second parameter. The thirdsecond function takes a launch policy as the first parameter and a callable object as the second parameter.
Effects:
  - The continuation `INVOKE(DECAY_COPY (std::forward<F>(func)))` is called when the object's shared state is ready (has a value or exception stored).
  - The continuation launches according to the specified policy or executor.
  - When the scheduler or launch policy is not provided the continuation inherits the parent's launch policy or executor.
  - Any value returned from the continuation is stored as the result in the shared state of the resulting `future`. Any exception propagated from the execution of the continuation is stored as the exceptional result in the shared state of the resulting `future`.
  - If the parent was created with `std::promise` (has no associated launch policy), the continuation behaves the same as the thirdsecond function with a policy argument of `launch::async | launch::deferred` and the same argument for `func`.
  - If the parent has a policy of `launch::deferred`, then it is filled by calling `wait()` or `get()` on the resulting `shared_future`. [ Note: This is similar to `future`. See example in 2.2. — end note ]
Returns:
  The return type of `then` depends on the return type of the closure `func` as defined below:
  - When `result_of_t<decay_t<F>()>` is `future<R>`, the function returns `future<R>`.
  - Otherwise, the function returns `future<result_of_t<decay_t<F>()>>`.
Notes:
  This is the same as in `future`. See the notes on `future::then` return type in 2.2.
Postconditions:
  - The `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;

Returns:
  `true` if the shared state is ready, `false` if it isn't.

2.4

# Function template `when_all`

[futures.when_all]

A new section 30.6.10 shall be inserted at the end of C++14 §30.6. Below is the content of that section.

    template
    see below when_all(InputIterator first, InputIterator last);

    template <typename... T>
    see below when_all(T&&... futures);

Requires:
  `T` is of type `future<R>` or `shared_future<R>`.
Notes:
  - There are two variations of `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.
  - Calling the first signature of `when_all` where `InputIterator` first equals last, returns a future with an empty vector that is immediately ready.
  - Calling the second signature of `when_any` with no arguments returns a `future<tuple<>>` that is immediately ready.
Effects:
  - Each `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.
  - The future returned by `when_all` will not throw an exception, but the futures held in the output collection may.
Returns:
  - `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`.
Postconditions:
  - All input `future<T>`s `valid() == false`.
  - All output `shared_future<T>` `valid() == true`.

2.5

# Function template `when_any`

[futures.when_any]

A new section 30.6.11 shall be inserted at the end of C++14 §30.6. Below is the content of that section.

    template <class InputIterator>
    see below when_any(InputIterator first, InputIterator last);

    template <typename... T>
    see below when_any(T&&... futures);

Requires:
  `T` is of type `future<R>` or `shared_future<R>`.
Notes:
  - There are two variations of `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.
  - Calling the first signature of `when_any` where `InputIterator` first equals last, returns a future with an empty vector that is immediately ready.
  - Calling the second signature of `when_any` with no arguments returns a `future<tuple<>>` that is immediately ready.
Effects:
  - Each `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.
  - The `future` returned by `when_any` will not throw an exception, but the futures held in the output collection may.
Returns:
  - `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`.
Postconditions:
  - All input `future<T>`s `valid() == false`.
  - All input `shared_future<T> valid() == true`.

2.6

# Function template `when_any_back`

[futures.when_any_back]

A new section 30.6.12 shall be inserted at the end of C++14 §30.6. Below is the content of that section.

    template <class InputIterator>
    see below when_any_back(InputIterator first, InputIterator last);

Requires:
  `InputIterator`'s value type shall be convertible to `future<R>` or `shared_future<R>`. All `R` types must be the same.
Notes:
  - The function `when_any_back` takes a pair of `InputIterators`.
  - Calling `when_any_back` where `InputIterator` first equals last, returns a `future` with an empty vector that is immediately ready.
Effects:
  - Each `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`.
  - After the copy, the `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.
  - The `future` returned by `when_any_back` will not throw an exception, but the futures held in the output collection may.
Returns:
  - `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`.
Postconditions:
  - All input `future<T>`s `valid() == false`.
  - All input `shared_future valid() == true`.

2.7

# Function template `make_ready_future`

[futures.make_ready_future]

A new section 30.6.13 shall be inserted at the end of C++14 §30.6. Below is the content of that section.

      template <typename T>
      future<decay_t<T>> make_ready_future(T&& value);

      future<void> make_ready_future();

Effects:
  The value that is passed in to the function is moved to the shared state of the returned `future` if it is an rvalue. Otherwise the value is copied to the shared state of the returned `future`.
Returns:
  - `future<decay_t<T>>`, if function is given a value of type `T`.
  - `future<void>`, if the function is not given any inputs.
Postconditions:
  - Returned `future<decay_t<T>>, valid() == true`.
  - Returned `future<decay_t<T>>, is_ready() == true`.

?

# Function template `async`

[futures.async]

Change C++14 §30.6.8 paragraph 1 as follows:

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);

    template<class F, class... Args>
    future<result_of_t<decay_t<F>(decay_t<Args>...)>>
    async(executor& ex, F&& f, Args&&... args);

Change C++14 §30.6.8 paragraph 3 as follows:
Effects:
  The first function behaves the same as a call to the second function with a policy argument of `launch::async | launch::deferred` and the same arguments for `F` and `Args`. The second and third functions creates a shared state that is associated with the returned future object. The further behavior of the second function depends on the policy argument as follows (if more than one of these conditions applies, the implementation may choose any of the corresponding policies):
  - if `policy & 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.
  - if `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. [ Note: If this policy is specified together with other policies, such as when using a policy value of `launch::async | launch::deferred`, implementations should defer invocation or the selection of the policy when no more concurrency can be effectively exploited. — end note ]
  - If no value is set in the launch policy, or a value is set that is neither specified in this International Standard or by the implementation, the behaviour is undefined.
The further behavior of the third function is as follows:

> The `executor::add()` function is given a `function<void()>` which calls `INVOKE (DECAY_COPY (std::forward<F>(f)) DECAY_COPY (std::forward<Args>(args))...)`. The implementation of the executor is decided by the programmer.