August 19, 2020·4 min read·engineeringcppexecutorslifetimestandards

A polymorphic executor needs an explicit lifetime contract

P2205R0 reviews any_executor and its property interface. Type erasure leaves important questions about target ownership, supported operations, and construction constraints.

A runtime-selected executor needs a common interface, but erasing its concrete type does not erase its resource obligations. The August 19 paper P2205R0 reviews the proposed any_executor wrapper and asks for clearer definitions of its behavior. Its concerns include the target's lifetime and the capabilities exposed through the wrapper.

The review is part of the work on the unified executors proposal, rather than an announcement of a finished standard facility. Its recommendations identify changes the authors support alongside issues that still require discussion. Those states matter when interpreting its examples.

RAII is a strong reason for me to use C++, particularly when a wrapper makes resource ownership explicit. A polymorphic executor should preserve that clarity even when the caller cannot name the concrete executor type. Otherwise, the convenience of runtime selection hides the lifetime question that the program still has to answer.

The target can carry a resource obligation

The paper describes a polymorphic wrapper whose target can be shared. It also notes that an executor object can represent a resource, including tracked outstanding work. Destroying or clearing the wrapper therefore has a meaning beyond removing an inert collection of function pointers.

This distinction affects a factory interface. A factory can choose an executor at runtime and return the common wrapper. The caller then needs to understand what the returned value keeps alive, and what changes when the caller releases it.

The wrapper's own lifetime is only one part of that account. Work already submitted can have a separate duration. Releasing a handle does not automatically mean that every operation associated with it has completed. The interface needs to describe that relationship.

For the application, this becomes a resource protocol. A value can represent access to execution, participation in an execution context's lifetime, or both. The caller cannot safely infer which interpretation applies from the fact that the wrapper is copyable.

An explicit clearing operation communicates intent

P2205R0 discusses the choice between models associated with std::function and std::any. One practical issue is how a user expresses releasing the stored target. The paper considers whether a named operation would make that intention clearer than assigning an empty value.

The underlying design question is useful even apart from the final spelling. A caller sometimes wants to replace the target, and sometimes wants to release it without replacement. The interface should make both actions understandable.

This is especially important when releasing the target affects tracked work. A statement that appears to assign an empty wrapper may participate in a larger shutdown protocol. A named release operation can help a reader identify the intended resource transition.

It does not, however, establish the whole shutdown protocol by itself. The specification still needs to say which resources the target owns and which operations remain outstanding. An expressive name improves the call site only when it refers to defined behavior.

Properties survive erasure selectively

The earlier properties review P2183R0 explains how properties configure execution and how required behavior differs from a preference. A polymorphic wrapper needs to preserve an appropriate property contract through its public type.

This creates a deliberate loss of information. A concrete executor can support operations that the wrapper does not advertise. Once an algorithm receives the wrapper, it should rely on the advertised interface. Reaching through it to recover concrete details would weaken the point of the abstraction.

The review therefore examines construction constraints between wrappers and from concrete executors. A successful conversion needs to establish that the destination wrapper can support its promised operations. Matching a convenient representation is insufficient.

That requirement is useful to the caller because it moves incompatibility to the boundary. An algorithm accepting a particular wrapper can then reason about the capabilities that wrapper guarantees. It does not need a separate runtime investigation of every executor that a factory might return.

Constraints also need to avoid recursive questions

P2205R0 presents an issue involving a wrapper's converting constructor and the executor concept. Its example needs to exclude the wrapper's own type before evaluating the more general executor requirement. The review calls attention to the order and form of those constraints.

The practical problem is that determining whether a type satisfies an interface can itself involve determining whether that type is constructible. A general converting constructor can make those questions refer back to each other. The constraint needs to establish a simpler case before entering the recursive one.

That is a useful reminder about C++ generic interfaces. A shorter constraint spelling does not guarantee a simpler instantiation process. The library author still needs an account of which questions its requirements cause the compiler to ask.

I regard the review's request for explicit terminology and design rationale as part of the technical work. A runtime wrapper needs a precise target-lifetime model and a capability contract that survives type erasure. Those definitions let callers understand what they retain and what they release without knowing the concrete executor chosen by the factory.