This paper proposes a programming model for executors, which are modular components for creating execution. The design of this proposal is described in paper P0761.

0.1 Changelog

0.1.1 Revision 9

As directed by the SG1/LEWG straw poll taken during the 2018 Bellevue executors meeting, we have separated The Unified Executors programming model proposal into two papers. This paper contains material related to one-way execution which the authors hope to standardize with C++20 as suggested by the Bellevue poll. P1244 contains remaining material related to dependent execution. We expect P1244 to evolve as committee consensus builds around a design for dependent execution.

This revision also contains bug fixes to the allocator_t property which were originally scheduled for Revision 7 but were inadvertently omitted.

0.1.2 Revision 8

Revision 8 of this proposal makes interface-changing properties such as oneway mutually exclusive in order to simplify implementation requirements for executor adaptors such as polymorphic executors. Additionally, this revision clarifies wording regarding execution agent lifetime.

0.1.3 Revision 7

Revision 7 of this proposal corrects wording bugs discovered by the authors after Revision 6's publication.

Enhanced static_query_v to result in a default property value for executors which do not provide a query function for the property of interest

0.1.10 Revision 0

1 Proposed Wording

1.1 Execution Support Library

1.1.1 General

(The following definition appears in working draft N4762 [thread.req.lockable.general])

An execution agent is an entity such as a thread that may perform work in parallel with other execution agents. [Note: Implementations or users may introduce other kinds of agents such as processes or thread-pool tasks. --end note] The calling agent is determined by context; e.g., the calling thread that contains the call, and so on.

An execution agent invokes a function object within an execution context such as the calling thread or thread-pool. An executor submits a function object to an execution context to be invoked by an execution agent within that execution context. [Note: Invocation of the function object may be inlined such as when the execution context is the calling thread, or may be scheduled such as when the execution context is a thread-pool with task scheduler. --end note] An executor may submit a function object with execution properties that specify how the submission and invocation of the function object interacts with the submitting thread and execution context, including forward progress guarantees [intro.progress].

For the intent of this library and extensions to this library, the lifetime of an execution agent begins before the function object is invoked and ends after this invocation completes, either normally or having thrown an exception.

1.2 Requirements

1.2.1 Customization point objects

(The following text has been adapted from the draft Ranges Technical Specification.)

A customization point object is a function object (C++ Std, [function.objects]) with a literal class type that interacts with user-defined types while enforcing semantic requirements on that interaction.

The type of a customization point object shall satisfy the requirements of CopyConstructible (C++Std [copyconstructible]) and Destructible (C++Std [destructible]).

All instances of a specific customization point object type shall be equal.

Let t be a (possibly const) customization point object of type T, and args... be a parameter pack expansion of some parameter pack Args.... The customization point object t shall be invocable as t(args...) when the types of Args... meet the requirements specified in that customization point object's definition. Otherwise, T shall not have a function call operator that participates in overload resolution.

Each customization point object type constrains its return type to satisfy some particular type requirements.

The library defines several named customization point objects. In every translation unit where such a name is defined, it shall refer to the same instance of the customization point object.

[Note: Many of the customization points objects in the library evaluate function call expressions with an unqualified name which results in invoking a user-defined function found by argument dependent name lookup (C++Std [basic.lookup.argdep]). To preclude such an expression resulting in invoking an unconstrained functions with the same name in namespace std, customization point objects specify that lookup for these expressions is performed in a context that includes deleted overloads matching the signatures of overloads defined in namespace std. When the deleted overloads are viable, user-defined overloads must be more specialized (C++Std [temp.func.order]) to be used by a customization point object. --end note]

1.2.2ProtoAllocator requirements

A type A meets the ProtoAllocator requirements if A is CopyConstructible (C++Std [copyconstructible]), Destructible (C++Std [destructible]), and allocator_traits<A>::rebind_alloc<U> meets the allocator requirements (C++Std [allocator.requirements]), where U is an object type. [Note: For example, std::allocator<void> meets the proto-allocator requirements but not the allocator requirements. --end note] No comparison operator, copy operation, move operation, or swap operation on these types shall exit via an exception.

None of these concepts' operations, nor an executor type's associated execution functions, associated query functions, or other member functions defined in executor type requirements, 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 x1.query(p) == x2.query(p) for every property p where both x1.query(p) and x2.query(p) are well-formed and result in a non-void type that is EqualityComparable (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 execution::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]

1.2.4OneWayExecutor requirements

A type X satisfies the OneWayExecutor requirements if it satisfies the general requirements on executors, as well as the requirements in the Table below.

[Note:OneWayExecutors provides fire-and-forget semantics without a channel for awaiting the completion of a submitted function object and obtaining its result. --end note]

In the Table below,

x denotes a (possibly const) executor object of type X,

cf denotes the function object DECAY_COPY(std::forward<F>(f))

f denotes a function object of type F&& invocable as cf() and where decay_t<F> satisfies the MoveConstructible requirements.

Expression

Return Type

Operational semantics

x.execute(f)

void

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 and the forward progress guarantee of the associated execution agent(s) are implementation defined. --end note.]

1.2.5BulkOneWayExecutor requirements

The BulkOneWayExecutor requirements specify requirements for executors which submit a function object to be invoked multiple times without a channel for awaiting the completion of the submitted function object invocations and obtaining their result. [Note: That is, the executor provides fire-and-forget semantics. --end note]

A type X satisfies the BulkOneWayExecutor requirements if it satisfies the general requirements on executors, as well as the requirements in the Table below.

i denotes a (possibly const) object whose type is executor_index_t<X>,

s denotes an object whose type is S,

cf denotes the function object DECAY_COPY(std::forward<F>(f)),

f denotes a function object of type F&& invocable as cf(i, s) and where decay_t<F> satisfies the MoveConstructible requirements,

Expression

Return Type

Operational semantics

x.bulk_execute(f, n, sf)

void

Evaluates DECAY_COPY(std::forward<F>(f)) on the calling thread to create a function object cf. [Note:* Additional copies of cf may subsequently be created. *--end note] For each value of i in shape ncf(i,s) (or copy of cf)) will be invoked at most once by an execution agent that is unique for each value of i. sf() will be invoked at most once to produce value s before any invocation of cf. May block pending completion of one or more invocations of cf. Synchronizes with (C++Std [intro.multithread]) the invocations of f. Shall not propagate any exception thrown by cf or any other function submitted to the executor. [Note: The treatment of exceptions thrown by bulk one-way submitted functions and the forward progress guarantee of the associated execution agent(s) are implementation defined. --end note.]

1.3 Executor customization points

When an executor customization point named NAME invokes a free execution function of the same name, overload resolution is performed in a context that includes the declaration voidNAME(auto&... args) = delete;, where sizeof...(args) is the arity of the free execution function. This context also does not include a declaration of the executor customization point.

[Note: This provision allows executor customization points to invoke the executor's free, non-member execution function of the same name without recursion. --end note]

Whenever std::execution::NAME(ARGS) is a valid expression, that expression satisfies the syntactic requirements for the free execution function named NAME with arity sizeof...(ARGS) with that free execution function's semantics.

1.3.1require

namespace {
constexpr unspecified require = unspecified;
}

The name require denotes a customization point. The effect of the expression std::execution::require(E, P0, Pn...) for some expressions E and P0, and where Pn... represents N expressions (where N is 0 or more), is equivalent to:

If N == 0, P0::is_requirable is true, and the expression decay_t<decltype(P0)>::template static_query_v<decay_t<decltype(E)>> == decay_t<decltype(P0)>::value() is a well-formed constant expression with value true, E.

If N == 0, P0::is_requirable is true, and the expression (E).require(P0) is well-formed, (E).require(P0).

If N == 0, P0::is_requirable is true, and the expression require(E, P0) is well-formed, require(E, P0).

1.3.2prefer

namespace {
constexpr unspecified prefer = unspecified;
}

The name prefer denotes a customization point. The effect of the expression std::execution::prefer(E, P0, Pn...) for some expressions E and P0, and where Pn... represents N expressions (where N is 0 or more), is equivalent to:

If N == 0, P0::is_preferable is true, and the expression decay_t<decltype(P0)>::template static_query_v<decay_t<decltype(E)>> == decay_t<decltype(P0)>::value() is a well-formed constant expression with value true, E.

If N == 0, P0::is_preferable is true, and the expression (E).require(P0) is well-formed, (E).require(P0).

If N == 0, P0::is_preferable is true, and the expression prefer(E, P0) is well-formed, prefer(E, P0).

1.3.4 Customization point type traits

This sub-clause contains templates that may be used to query the properties of a type at compile time. Each of these templates is a UnaryTypeTrait (C++Std [meta.rqmts]) with a BaseCharacteristic of true_type if the corresponding condition is true, otherwise false_type.

Template

Condition

Preconditions

template<class T>struct can_require

The expression std::execution::require( declval<const Executor>(), declval<Properties>()...) is well formed.

T is a complete type.

template<class T>struct can_prefer

The expression std::execution::prefer( declval<const Executor>(), declval<Properties>()...) is well formed.

T is a complete type.

template<class T>struct can_query

The expression std::execution::query( declval<const Executor>(), declval<Property>()) is well formed.

T is a complete type.

1.4 Executor properties

1.4.1 In general

An executor's behavior in generic contexts is determined by a set of executor properties, and each executor property imposes certain requirements on the executor.

Given an existing executor, a related executor with different properties may be created by invoking the require member or non-member functions. These functions behave according the Table below. In the Table below, x denotes a (possibly const) executor object of type X, and p denotes a (possibly const) property object.

[Note: As a general design note properties which define a mutually exclusive pair, that describe an enabled or non-enabled behaviour follow the convention of having the same property name for both with the not_ prefix to the property for the non-enabled behaviour. --end note]

Expression

Comments

x.require(p)require(x,p)

Returns an executor object with the requested property p added to the set. All other properties of the returned executor are identical to those of x, except where those properties are described below as being mutually exclusive to p. In this case, the mutually exclusive properties are implicitly removed from the set associated with the returned executor.

The expression is ill formed if an executor is unable to add the requested property.

The current value of an executor's properties can be queried by invoking the query function. This function behaves according the Table below. In the Table below, x denotes a (possibly const) executor object of type X, and p denotes a (possibly const) property object.

Expression

Comments

x.query(p)

Returns the current value of the requested property p. The expression is ill formed if an executor is unable to return the requested property.

1.4.2 Requirements on properties

A property type P shall provide:

A nested constant expression named is_requirable of type bool, usable as P::is_requirable.

A nested constant expression named is_preferable of type bool, usable as P::is_preferable.

[Note: These constants are used to determine whether the property can be used with the require and prefer customization points, respectively. --end note]

A property type P may provide a nested type polymorphic_query_result_type that satisfies the CopyConstructible and Destructible requirements. If P::is_requirable == true or P::is_preferable == true, polymorphic_query_result_type shall also satisfy the DefaultConstructible requirements. [Note: When present, this type allows the property to be used with a polymorphic executor wrapper. --end note]

A property type P may provide:

A nested variable template static_query_v, usable as P::static_query_v<Executor>. This may be conditionally present.

A member function value().

If both static_query_v and value() are present, they shall return the same type and this type shall satisfy the EqualityComparable requirements.

[Note: These are used to determine whether invoking require would result in an identity transformation. --end note]

S::static_query_v<Executor> is true if and only if Executor fulfills S's requirements.

The oneway_t and bulk_oneway_t properties are mutually exclusive.

1.4.4.1 Polymorphic wrappers

In several places in this section the operation CONTAINS_PROPERTY(p, pn) is used. All such uses mean std::disjunction_v<std::is_same<p, pn>...>.

In several places in this section the operation FIND_CONVERTIBLE_PROPERTY(p, pn) is used. All such uses mean the first type P in the parameter pack pn for which std::is_convertible_v<p, P> is true. If no such type P exists, the operation FIND_CONVERTIBLE_PROPERTY(p, pn) is ill-formed.

The nested class template S::polymorphic_executor_type conforms to the following specification.

Returns: If polymorphic_executor_type::query(e, p) is well-formed, static_cast<Property::polymorphic_query_result_type>(polymorphic_executor_type::query(e, p)), where e is the target object of *this. Otherwise, Property::polymorphic_query_result_type{}.

1.4.4.1.6polymorphic_executor_type capacity

explicit operator bool() const noexcept;

Returns:true if *this has a target, otherwise false.

1.4.4.1.7polymorphic_executor_type target access

const type_info& target_type() const noexcept;

Returns: If *this has a target of type T, typeid(T); otherwise, typeid(void).

1.4.4.1.10polymorphic_executor_type casts

Requires: The target object was first inserted into a polymorphic wrapper (whether via the wrapper's constructor or assignment operator) whose template parameters included the parameters in OtherSupportableProperties.

Returns: A polymorphic wrapper whose target is e.

1.4.4.2oneway_t customization points

In addition to conforming to the above specification for interface-changing properties, the oneway_t property provides the following customization:

Returns: A value e1 of type E1 that holds a copy of ex. E1 has member functions require and query that forward to the corresponding members of the copy of ex, if present. e1 has the same properties as ex, except for the addition of the oneway_t property and the exclusion of other interface-changing properties. The type E1 satisfies the OneWayExecutor requirements by implementing member function execute in terms of the member function bulk_execute of the object ex, and E1 has a member function Executor require(bulk_oneway_t) const that returns a copy of ex.

Remarks: This function shall not participate in overload resolution unless oneway_t::static_query_v<Executor> is false and bulk_oneway_t::static_query_v<Executor> is true.

1.4.4.3oneway_t polymorphic wrapper

In addition to conforming to the above specification for polymorphic wrappers, the nested class template oneway_t::polymorphic_executor_type provides the following member functions:

Returns: A value e1 of type E1 that holds a copy of ex. E1 has member functions require and query that forward to the corresponding members of the copy of ex, if present. e1 has the same properties as ex, except for the addition of the bulk_oneway_t property and the exclusion of other interface-changing properties. The type E1 satisfies the BulkOneWayExecutor requirements by implementing member function bulk_execute in terms of the member function execute of the object ex, and E1 has a member function Executor require(oneway_t) const that returns a copy of ex.

Remarks: This function shall not participate in overload resolution unless bulk_oneway_t::static_query_v<Executor> is false and oneway_t::static_query_v<Executor> is true.

1.4.4.5bulk_oneway_t polymorphic wrapper

In addition to conforming to the above specification for polymorphic wrappers, the nested class template bulk_oneway_t::polymorphic_executor_type has the following member functions:

Returns: A value e1 of type E1 that holds a copy of ex. If Executor satisfies the OneWayExecutor requirements, E1 shall satisfy the OneWayExecutor requirements by providing member functions require, query, and execute that forward to the corresponding member functions of the copy of ex. If Executor satisfies the BulkOneWayExecutor requirements, E1 shall satisfy the BulkOneWayExecutor requirements by providing member functions require, query, and bulk_execute that forward to the corresponding member functions of the copy of ex. In addition, E1 provides an overload of require such that e1.require(blocking.always) returns a copy of e1, an overload of query such that e1.query(blocking) returns blocking.always, and functions execute and bulk_execute shall block the calling thread until the submitted functions have finished execution. e1 has the same executor properties as ex, except for the addition of the blocking_t::always_t property, and removal of blocking_t::never_t and blocking_t::possibly_t properties if present.

Remarks: This function shall not participate in overload resolution unless blocking_adaptation_t::static_query_v<Executor> is blocking_adaptation.allowed.

1.4.5.2 Properties to indicate if blocking and directionality may be adapted

Returns: A value e1 of type E1 that holds a copy of ex. If Executor satisfies the OneWayExecutor requirements, E1 shall satisfy the OneWayExecutor requirements by providing member functions require, query, and execute that forward to the corresponding member functions of the copy of ex. If Executor satisfies the BulkOneWayExecutor requirements, E1 shall satisfy the BulkOneWayExecutor requirements by providing member functions require, query, and bulk_execute that forward to the corresponding member functions of the copy of ex. In addition, blocking_adaptation_t::static_query_v<E1> is blocking_adaptation.allowed, and e1.require(blocking_adaptation.disallowed) yields a copy of ex. e1 has the same executor properties as ex, except for the addition of the blocking_adaptation_t::allowed_t property.

The existence of the executor object does not indicate any likely future submission of a function object.

outstanding_work_t::tracked_t

outstanding_work::tracked

The existence of the executor object represents an indication of likely future submission of a function object. The executor or its associated execution context may choose to maintain execution resources in anticipation of this submission.

[Note: The outstanding_work_t::tracked_t and outstanding_work_t::untracked_t properties are used to communicate to the associated execution context intended future work submission on the executor. The intended effect of the properties is the behavior of execution context's facilities for awaiting outstanding work; specifically whether it considers the existance of the executor object with the outstanding_work_t::tracked_t property enabled outstanding work when deciding what to wait on. However this will be largely defined by the execution context implementation. It is intended that the execution context will define its wait facilities and on-destruction behaviour and provide an interface for querying this. An initial work towards this is included in P0737r0. --end note]

Execution agents within the same bulk execution may be parallelized and vectorized.

bulk_guarantee_t::sequenced_t

bulk_guarantee_t::sequenced

Execution agents within the same bulk execution may not be parallelized.

bulk_guarantee_t::parallel_t

bulk_guarantee_t::parallel

Execution agents within the same bulk execution may be parallelized.

Execution agents associated with the bulk_guarantee_t::unsequenced_t property may invoke the function object in an unordered fashion. Any such invocations in the same thread of execution are unsequenced with respect to each other. [Note: This means that multiple execution agents may be interleaved on a single thread of execution, which overrides the usual guarantee from [intro.execution] that function executions do not interleave with one another. --end note]

Execution agents associated with the bulk_guarantee_t::sequenced_t property invoke the function object in sequence in lexicographic order of their indices.

Execution agents associated with the bulk_guarantee_t::parallel_t property invoke the function object with a parallel forward progress guarantee. Any such invocations in the same thread of execution are indeterminately sequenced with respect to each other. [Note: It is the caller's responsibility to ensure that the invocation does not introduce data races or deadlocks. --end note]

[Editorial note: The descriptions of these properties were ported from [algorithms.parallel.user]. The intention is that a future standard will specify execution policy behavior in terms of the fundamental properties of their associated executors. We did not include the accompanying code examples from [algorithms.parallel.user] because the examples seem easier to understand when illustrated by std::for_each. --end editorial note]

1.4.5.6 Properties for mapping of execution on to threads

The mapping_t property describes what guarantees executors provide about the mapping of execution agents onto threads of execution.

[Note: A mapping of an execution agent onto a thread of execution implies the execution agent runs as-if on a std::thread. Therefore, the facilities provided by std::thread, such as thread-local storage, are available. mapping_t::new_thread_t provides stronger guarantees, in particular that thread-local storage will not be shared between execution agents. --end note]

For executor types that satisfy the OneWayExecutor requirements, the executor implementation shall use the encapsulated allocator to allocate any memory required to store the submitted function object.

allocator_t<void>

Specialisation of allocator_t<ProtoAllocator>.

For executor types that satisfy the OneWayExecutor requirements, the executor implementation shall use an implementation defined default allocator to allocate any memory required to store the submitted function object.

If the expression execution::query(E, P) is well formed, where P is an object of type allocator_t<ProtoAllocator>, then: * the type of the expression execution::query(E, P) shall satisfy the ProtoAllocator requirements; * the result of the expression execution::query(E, P) shall be the allocator currently established in the executor E; and * the expression execution::query(E, allocator_t<void>{}) shall also be well formed and have the same result as execution::query(E, P).

1.4.6.1allocator_t members

Returns: An allocator object whose exposition-only member a_ is initialized as a_(a).

Remarks: This function shall not participate in overload resolution unless ProtoAllocator is void.

[Note: It is permitted for a to be an executor's implementation-defined default allocator and, if so, the default allocator may also be established within an executor by passing the result of this function to require. --end note]

static constexpr ProtoAllocator value() const;

Returns: The exposition-only member a_.

Remarks: This function shall not participate in overload resolution unless ProtoAllocator is not void.

1.5 Executor type traits

1.5.1 Determining that a type satisfies executor type requirements

This sub-clause contains templates that may be used to query the properties of a type at compile time. Each of these templates is a UnaryTypeTrait (C++Std [meta.rqmts]) with a BaseCharacteristic of true_type if the corresponding condition is true, otherwise false_type.

However, if we simply specify execution::outstanding_work.tracked in the executor template parameter list, we will get a compile error due to the executor template not knowing that execution::outstanding_work.tracked is intended for use with prefer only. At the point of construction from an inline_executor called ex, executor will try to instantiate implementation templates that perform the ill-formed execution::require(ex, execution::outstanding_work.tracked).

The prefer_only adapter addresses this by turning off the is_requirable attribute for a specific property. It would be used in the above example as follows:

If InnerProperty::polymorphic_query_result_type is valid and denotes a type, the template instantiation prefer_only<InnerProperty> defines a nested type polymorphic_query_result_type as a synonym for InnerProperty::polymorphic_query_result_type.

If InnerProperty::static_query_v is a variable template and InnerProperty::static_query_v<E> is well formed for some executor type E, the template instantiation prefer_only<InnerProperty> defines a nested variable template static_query_v as a synonym for InnerProperty::static_query_v.

Remarks: Shall not participate in overload resolution unless std::is_same_v<Property, prefer_only> is true, and the expression execution::query(ex, p.property) is well-formed.

1.7 Thread pools

Thread pools manage execution agents which run on threads without incurring the overhead of thread creation and destruction whenever such agents are needed.

1.7.1 Header <thread_pool> synopsis

namespace std {
class static_thread_pool;
} // namespace std

1.7.2 Class static_thread_pool

static_thread_pool is a statically-sized thread pool which may be explicitly grown via thread attachment. The static_thread_pool is expected to be created with the use case clearly in mind with the number of threads known by the creator. As a result, no default constructor is considered correct for arbitrary use cases and static_thread_pool does not support any form of automatic resizing.

static_thread_pool presents an effectively unbounded input queue and the execution functions of static_thread_pool's associated executors do not block on this input queue.

For an object of type static_thread_pool, outstanding work is defined as the sum of:

the number of existing executor objects associated with the static_thread_pool for which the execution::outstanding_work.tracked property is established;

the number of function objects that have been added to the static_thread_pool via the static_thread_pool executor, but not yet invoked; and

the number of function objects that are currently being invoked within the static_thread_pool.

The static_thread_pool member functions executor, attach, wait, and stop, and the associated executors' copy constructors and member functions, do not introduce data races as a result of concurrent invocations of those functions from different threads of execution.

A static_thread_pool's threads run execution agents with forward progress guarantee delegation. [Note: Forward progress is delegated to an execution agent for its lifetime. Because static_thread_pool guarantees only parallel forward progress to running execution agents; i.e., execution agents which have run the first step of the function object. --end note]

1.7.2.1 Types

using executor_type = see-below;

An executor type conforming to the specification for static_thread_pool executor types described below.

1.7.2.2 Construction and destruction

static_thread_pool(std::size_t num_threads);

Effects: Constructs a static_thread_pool object with num_threads threads of execution, as if by creating objects of type std::thread.

~static_thread_pool();

Effects: Destroys an object of class static_thread_pool. Performs stop() followed by wait().

1.7.2.3 Worker management

void attach();

Effects: Adds the calling thread to the pool such that this thread is used to execute submitted function objects. [Note: Threads created during thread pool construction, or previously attached to the pool, will continue to be used for function object execution. --end note] Blocks the calling thread until signalled to complete by stop() or wait(), and then blocks until all the threads created during static_thread_pool object construction have completed. (NAMING: a possible alternate name for this function is join().)

void stop();

Effects: Signals the threads in the pool to complete as soon as possible. If a thread is currently executing a function object, the thread will exit only after completion of that function object. Invocation of stop() returns without waiting for the threads to complete. Subsequent invocations to attach complete immediately.

void wait();

Effects: If not already stopped, signals the threads in the pool to complete once the outstanding work is 0. Blocks the calling thread (C++Std [defns.block]) until all threads in the pool have completed, without executing submitted function objects in the calling thread. Subsequent invocations of attach() complete immediately.

Synchronization: The completion of each thread in the pool synchronizes with (C++Std [intro.multithread]) the corresponding successful wait() return.

1.7.2.4 Executor creation

executor_type executor() noexcept;

Returns: An executor that may be used to submit function objects to the thread pool. The returned executor has the following properties already established:

execution::oneway

execution::blocking.possibly

execution::relationship.fork

execution::outstanding_work.untracked

execution::allocator

execution::allocator(std::allocator<void>())

1.7.3static_thread_pool executor types

All executor types accessible through static_thread_pool::executor(), and subsequent invocations of the member function require, conform to the following specification.

Returns: An executor object of an unspecified type conforming to these specifications, associated with the same thread pool as *this, and having the requested property established. When the requested property is part of a group that is defined as a mutually exclusive set, any other properties in the group are removed from the returned executor object. All other properties of the returned executor object are identical to those of *this.

see-below require(const execution::allocator_t<void>& a) const;

Returns:require(execution::allocator(x)), where x is an implementation-defined default allocator.

Returns: An executor object of an unspecified type conforming to these specifications, associated with the same thread pool as *this, with the execution::allocator_t<ProtoAllocator> property established such that allocation and deallocation associated with function submission will be performed using a copy of a.alloc. All other properties of the returned executor object are identical to those of *this.

Returns: The allocator object associated with the executor, with type and value as either previously established by the execution::allocator_t<ProtoAllocator> property or the implementation defined default allocator established by the execution::allocator_t<void> property.

bool running_in_this_thread() const noexcept;

Returns:true if the current thread of execution is a thread that was created by or attached to the associated static_thread_pool object.

1.7.3.4 Comparisons

bool operator==(const C& a, const C& b) noexcept;

Returns:true if &a.query(execution::context) == &b.query(execution::context) and a and b have identical properties, otherwise false.

Effects: Submits the function f for execution on the static_thread_pool according to the OneWayExecutor requirements and the properties established for *this. If the submitted function f exits via an exception, the static_thread_pool invokes std::terminate().

Effects: Submits the function f for bulk execution on the static_thread_pool according to the BulkOneWayExecutor requirements and the properties established for *this. If the submitted function f exits via an exception, the static_thread_pool invokes std::terminate().