### Executor requirements

##### Standard executors

Let `executor-of-impl` be the exposition-only concept

    template<class E, class F>
      concept executor-of-impl =
        invocable<decay_t<F>&> &&
        constructible_from<decay_t<F>, F> &&
        move_constructible<decay_t<F>> &&
        copy_constructible<E> &&
        is_nothrow_copy_constructible_v<E> &&
        equality_comparable<E> /* nothrow */ &&
        requires(const E& e, F&& f) {
          execution::execute(e, (F&&)f);
        };

Then the `executor` and `executor_of` concepts are defined as follows:

    template<class E>
      concept executor =
        executor-of-impl<E, execution::invocable_archetype>;

    template<class E, class F>
      concept executor_of =
        executor<E> &&
        executor-of-impl<E, F>;

Neither an executor's equality comparison nor `swap` operation shall exit via an exception.

None of an executor type's copy constructor, destructor, equality comparison, `swap` function, `execute` function, or associated `query` functions shall introduce data races as a result of concurrent invocations of those functions from different threads.

For any two (possibly const) values `x1` and `x2` of some executor type `X`, `x1 == x2` shall return `true` only if `asio::query(x1,p) == asio::query(x2,p)` for every property `p` where both `asio::query(x1,p)` and `asio::query(x2,p)` are well-formed and result in a non-void type that is `equality_comparable` (C++Std [equalitycomparable]). [Note: The above requirements imply that `x1 == x2` returns `true` if `x1` and `x2` can be interchanged with identical effects. An executor may conceptually contain additional properties which are not exposed by a named property type that can be observed via `asio::query`; in this case, it is up to the concrete executor implementation to decide if these properties affect equality. Returning `false` does not necessarily imply that the effects are not identical. —end note]

An executor type's destructor shall not block pending completion of the submitted function objects. [Note: The ability to wait for completion of submitted function objects may be provided by the associated execution context. —end note]

In addition to the above requirements, types `E` and `F` model `executor_of` only if they satisfy the requirements of the Table below.

Let:

- `e` denotes a (possibly const) executor object of type `E`,
- `cf` denotes the function object `DECAY_COPY(std::forward<F>(f))`
- `f` denotes a function of type `F&&` invocable as `cf()` and where `decay_t<F>` models `move_constructible`.

The expression `execution::execute(e, f)`:

- Evaluates `DECAY_COPY(std::forward<F>(f))` on the calling thread to create `cf` that will be invoked at most once by an execution agent.
- May block pending completion of this invocation. Synchronizes with [intro.multithread] the invocation of `f`.
- Shall not propagate any exception thrown by the function object or any other function submitted to the executor.

[Note: The treatment of exceptions thrown by one-way submitted functions is implementation-defined. The forward progress guarantee of the associated execution agent(s) is implementation-defined. —end note]

##### Networking TS-style executors

The library describes a standard set of requirements for executors. A type meeting the `Executor` requirements embodies a set of rules for determining how submitted function objects are to be executed.

A type `X` meets the `Executor` requirements if it satisfies the requirements of `CopyConstructible` (C++Std [copyconstructible]) and `Destructible` (C++Std [destructible]), as well as the additional requirements listed below.

No constructor, comparison operator, copy operation, move operation, swap operation, or member functions `context`, `on_work_started`, and `on_work_finished` on these types shall exit via an exception.

The executor copy constructor, comparison operators, and other member functions defined in these requirements shall not introduce data races as a result of concurrent calls to those functions from different threads.

Let `ctx` be the execution context returned by the executor's `context()` member function. An executor becomes invalid when the first call to `ctx.shutdown()` returns. The effect of calling `on_work_started`, `on_work_finished`, `dispatch`, `post`, or `defer` on an invalid executor is undefined. [Note: The copy constructor, comparison operators, and `context()` member function continue to remain valid until `ctx` is destroyed. —end note]

In the table below, `x1` and `x2` denote (possibly const) values of type `X`, `mx1` denotes an xvalue of type `X`, `f` denotes a `MoveConstructible` (C++Std [moveconstructible]) function object callable with zero arguments, `a` denotes a (possibly const) value of type `A` meeting the `Allocator` requirements (C++Std [allocator.requirements]), and `u` denotes an identifier.

Table 15. Executor requirements

expression | type | assertion/note pre/post-conditions
--- | --- | ---
`X u(x1);` |  | Shall not exit via an exception. post: `u == x1` and `std::addressof(u.context()) == std::addressof(x1.context()).`
`X u(mx1);` |  | Shall not exit via an exception. post: `u` equals the prior value of `mx1` and `std::addressof(u.context())` equals the prior value of `std::addressof(mx1.context())`.
`x1 == x2` | `bool` | Returns `true` only if `x1` and `x2` can be interchanged with identical effects in any of the expressions defined in these type requirements. [Note: Returning `false` does not necessarily imply that the effects are not identical. —end note] `operator==` shall be reflexive, symmetric, and transitive, and shall not exit via an exception.
`x1 != x2` | `bool` | Same as `!(x1 == x2)`.
`x1.context()` | `execution_context&`, or `E&` where `E` is a type that satifisfies the `ExecutionContext` requirements. | Shall not exit via an exception. The comparison operators and member functions defined in these requirements shall not alter the reference returned by this function.
`x1.on_work_started()` |  | Shall not exit via an exception.
`x1.on_work_finished()` |  | Shall not exit via an exception. Precondition: A preceding call `x2.on_work_started()` where `x1 == x2`.
`x1.dispatch(std::move(f),a)` |  | Effects: Creates an object `f1` initialized with `DECAY_COPY``(forward<Func>(f))` (C++Std [thread.decaycopy]) in the current thread of execution . Calls `f1()` at most once. The executor may block forward progress of the caller until `f1()` finishes execution. Executor implementations should use the supplied allocator to allocate any memory required to store the function object. Prior to invoking the function object, the executor shall deallocate any memory allocated. [Note: Executors defined in this Technical Specification always use the supplied allocator unless otherwise specified. —end note] Synchronization: The invocation of `dispatch` synchronizes with (C++Std [intro.multithread]) the invocation of `f1`.
`x1.post(std::move(f),a)` `x1.defer(std::move(f),a)` |  | Effects: Creates an object `f1` initialized with `DECAY_COPY``(forward<Func>(f))` in the current thread of execution. Calls `f1()` at most once. The executor shall not block forward progress of the caller pending completion of `f1()`. Executor implementations should use the supplied allocator to allocate any memory required to store the function object. Prior to invoking the function object, the executor shall deallocate any memory allocated. [Note: Executors defined in this Technical Specification always use the supplied allocator unless otherwise specified. —end note] Synchronization: The invocation of `post` or `defer` synchronizes with (C++Std [intro.multithread]) the invocation of `f1`. [Note: Although the requirements placed on `defer` are identical to `post`, the use of `post` conveys a preference that the caller does not block the first step of `f1`'s progress, whereas `defer` conveys a preference that the caller does block the first step of `f1`. One use of `defer` is to convey the intention of the caller that `f1` is a continuation of the current call context. The executor may use this information to optimize or otherwise adjust the way in which `f1` is invoked. —end note]