Modules and coroutines enter the C++20 draft
Kona adopts two language mechanisms that move recurring library and build conventions into explicit compiler-supported contracts.
A header's contents depend on the preprocessor state in which it is included. An asynchronous function expressed through callbacks often distributes its local state across several objects. These are different problems, but both require conventions outside an ordinary function declaration. At the Kona meeting, WG21 adopted modules and coroutines for C++20, as Herb Sutter reports in his February 23 meeting account.
Their adoption changes the working draft. It does not mean every compiler or build system now provides an interchangeable implementation. The interesting development is that the language has chosen mechanisms for these concerns, giving implementers and library authors a common direction.
My strongest argument for C++ remains RAII. Scope-based destruction makes ownership visible in a way that can survive exceptions. Coroutines interest me partly because suspension changes how long such scopes remain active. Modules address another cost I associate with the language, the work required to compile and understand interfaces mixed with implementation machinery.
Modules distinguish an interface from included text
Richard Smith's P1103R3 merging paper, dated February 22, describes the merged modules design. Its history includes the Modules TS and the alternative Atom proposal. Combining those directions required decisions about how modular code interacts with existing headers.
A module interface explicitly identifies its module and the declarations it exports. A consumer imports that module. Under the adopted direction, a small interface can look as follows:
export module arithmetic;
export int twice(int value)
{
return value * 2;
}
A separate translation unit can name that interface. This consumer assumes that its argument remains within the range where multiplication is representable:
import arithmetic;
int main()
{
return twice(3) == 6 ? 0 : 1;
}
These snippets illustrate the language relationship. They deliberately omit a compiler command because producing and locating the compiled interface is a toolchain integration question. Naming the module in source does not define a universal filename convention or command-line protocol for its build product.
The export declaration also supplies a different kind of information from a header guard. A guard controls repeated inclusion within a preprocessing context. Export selects which declarations become available by name lookup to an importing consumer. Those are distinct responsibilities, even when both reduce problems associated with large collections of headers.
Visibility and reachability are separate
The modules paper distinguishes a declaration that is visible to name lookup from one whose semantic effects are reachable. A consumer may need a type's definition to understand an exported interface without gaining unrestricted lookup access to every implementation name involved.
That distinction is necessary in a language with templates and type definitions. Hiding a name from direct lookup cannot always mean discarding every semantic fact about the entity. An imported interface still has to provide the information needed to compile legitimate uses of what it exports.
P1103R3 illustrates an exported function implemented using a non-exported helper. Another unit in the same module can see that helper under the design's rules. An unrelated importing unit can call the exported function while failing to find the helper by name.
The practical implication is a real interface decision. Library authors choose the names consumers can use, while the compiler retains semantic information needed to make those names work. This makes modules more than a shorthand for locating declarations. It gives the language an explicit account of an interface boundary.
I find that distinction useful because C++ abstraction often depends on implementation details being available to compilation. A mechanism that can retain necessary semantics while limiting lookup offers a more precise contract than expecting every consumer to understand which names in an included file are unofficial.
Existing headers still need an integration route
The merged design provides a global module fragment for preprocessing directives before the module declaration. That permits a module unit to include existing headers while keeping their declarations outside the module's ownership. The paper also describes header units, which process a named header and make its interface available for import.
Those routes do not have identical macro behavior. The paper says a direct header-unit import can make its saved macros available, while re-exporting that header unit does not re-export the macros. A statement that modules simply eliminate all macro interaction would miss this compatibility mechanism.
The different routes matter to incremental adoption. An existing library can depend on configuration macros, and a module interface may still need declarations from it. A language design that required rewriting every dependency first would make migration depend on work outside the library author's control.
This compatibility also explains why build systems remain involved. An import creates a dependency on information produced from another source unit. Tooling has to discover and satisfy that dependency. The language specifies what an import means, while the build needs a method for obtaining the required compiled interface before compiling the consumer.
Coroutines preserve local state across suspension
The earlier Coroutines TS draft N4760 provides the background for the adopted coroutine direction. It introduces co_await, co_yield and co_return, together with the library protocol that supports them. Sutter's meeting report identifies Gor Nishanov's design as the one adopted and notes the work of other proposal authors in reviewing the tradeoffs.
The fundamental operation is suspension within a function. A coroutine can return control to its caller or resumer while retaining state needed for a later continuation. That lets source code express a sequence around an asynchronous operation without requiring each continuation to appear as a separate callback body.
Suspension is different from leaving the scope. The TS explicitly preserves objects while a block is suspended and notes that suspension is not scope exit. An RAII object can therefore remain alive while execution is elsewhere. Its destructor still has a role, but the programmer must understand when the containing coroutine state will be destroyed.
That is particularly consequential for a resource acquired before an await. A scope that visually spans only a few lines can stay active for the entire suspended interval. A lock or file owned by a local object retains the lifetime described by that scope, rather than acquiring a shorter lifetime because the function yielded control.
The library supplies the await behavior
The coroutine protocol gives an awaiter operations that decide whether suspension is necessary, arrange what happens at suspension and produce the result on resumption. In the TS, these are named await_ready, await_suspend and await_resume. The language uses that protocol to connect the source expression with the surrounding library.
A ready result can avoid suspension. A result requiring later work can arrange resumption through the suspension protocol. That means co_await does not independently choose a worker thread or define an event loop. The operation and its supporting types supply the relevant behavior.
The promise object provides another set of customization points, including producing the return object and defining initial and final suspension behavior. The return type is consequently part of the coroutine machinery. A coroutine's short body depends on a library contract that an ordinary return statement does not require.
This is consistent with a recurring C++ approach. The compiler recognizes a language construct, then uses specified library operations to give it application-appropriate behavior. It allows different coroutine abstractions, but users still need to know the semantics of the return type and awaitable operations they select.
The adoption should therefore be read as agreement on foundational machinery. It does not make every asynchronous library interoperate automatically, and the meeting report separately says executors and networking did not make the C++20 cut. A coroutine mechanism can support asynchronous code before those broader library interfaces settle.
Two additions with different adoption costs
Modules primarily change how declarations and their dependencies reach a translation unit. Coroutines change the execution and lifetime structure of a function. Treating both as equally local syntax changes would underestimate their integration requirements.
A module author must decide what to export and how to accommodate non-modular dependencies. A coroutine library author must define ownership of suspended state and the protocol for obtaining results. In both cases, a compact use site depends on a contract established elsewhere.
The Kona adoption gives those contracts a place in the C++20 working draft. For modules, the merged paper explains the important distinction between lookup visibility and semantic reachability. For coroutines, the suspension model makes retained local state part of the language mechanism. Those are the details that determine how the additions can support existing C++ practices, including separate compilation and scope-based resource management.