December 15, 2020·6 min read·engineeringcppstandardsmodulesinterfaces

ISO publishes C++20 as ISO/IEC 14882

ISO/IEC 14882:2020 is published on December 15. Modules illustrate how the revision gives interfaces a language-level boundary while leaving implementation choices to toolchains.

A published standard gives implementers and programmers an edition they can identify precisely when discussing a language rule. The IEC catalogue entry for ISO/IEC 14882:2020 records publication on December 15. C++20 now has its published international-standard designation after the committee's technical work and the approval ballot earlier in the year.

The September approval announcement described remaining editorial work before publication. Today's milestone completes that progression. It does not mean that every implementation has completed every facility, or that a product automatically gains those facilities by updating a document reference.

The useful technical question is what the revision lets an interface express. Modules are a substantial example because they put an explicit import and export structure into the language. That changes how a component describes the names it provides to another translation unit.

C++ already asks readers to understand several historical forms of interface construction. I am interested in modules where they make the intended boundary easier to identify. Their value needs to be explained through that boundary and its requirements, rather than assumed from a promise of faster compilation.

A module names what it exports

The public April working draft N4861 documents the module interface and import model developed for this revision. It remains a working draft, so it should be identified as such when used to examine the design. The published standard is the edition named in the catalogue.

A minimal interface can identify its module and export a function definition. Here the module name and the exported operation are both explicit in the source.

export module arithmetic;

export int increment(int value)
{
    return value + 1;
}

A consumer imports the module. Its source names the dependency directly before using the operation that the interface exports.

import arithmetic;

int main()
{
    return increment(4) == 5 ? 0 : 1;
}

The example uses only a value whose increment remains representable. It demonstrates the source-level interface relationship, not a portable compiler command for producing and locating every module artifact.

The exported declaration identifies a name available to the importing translation unit. That is more explicit than asking the consumer to infer an intended interface from all the declarations reached through textual inclusion. The module author has a language construct for the boundary.

This still requires ordinary API design. Exporting a function does not establish that its preconditions are suitable or that its name explains the operation. The function body can contain a defect independently of whether the module boundary is correct.

The language boundary and the build boundary cooperate

An import introduces a dependency on the module interface. The consumer needs the relevant information available when the compiler processes that dependency. The build must arrange the order and the compiler inputs needed to provide it.

That follows from the interface relationship. A source declaration naming a module does not create its implementation artifacts by itself. The build system still has to describe which sources provide the module and how the consumer obtains the resulting compiler information.

This distinction matters to an adoption plan because replacing an include line is only one source edit. The component also needs a build representation of the new dependency. An existing build that treats all translation units as independent tasks may need more information about their relationships.

The standard's source semantics give the build a definite relationship to implement. They do not establish that every compiler uses the same artifact format or command-line convention. A project needs to use its supported toolchain's documented implementation of that relationship.

For a library distributed as source, that can affect the instructions given to consumers. The documentation should identify the module interface and the supported build path. A consumer then has enough information to distinguish a missing dependency from a language-level error in its own source.

Export is more precise than an informal public convention

Richard Smith's earlier P1103R3 modules merge paper describes the interface in terms of exported declarations and the names they make available. That design gives a component a way to distinguish names intended for importers from other declarations in its implementation.

An informal naming convention can communicate a similar intention to human readers, but it does not by itself give the compiler the same boundary. The module declaration makes that intention part of the program's structure.

A library author still has to examine what its exported declarations depend on. A function signature can expose types that consumers need to understand. Templates can require definitions and operations relevant to instantiation. An explicit export boundary makes those choices more visible without making them disappear.

The choice also affects how a reader navigates the component. An exported operation provides an intended entry into its behavior. Internal declarations can explain how the implementation fulfills that behavior without all becoming separate promises to callers. This gives maintenance a more definite question when a helper changes. The author can ask whether the change affects the exported contract, including the types and requirements through which callers use it. That is a source-level consequence of a deliberate interface boundary, independent of any claim about compilation speed.

This is particularly useful when an implementation evolves. A private helper can change without necessarily changing the interface's stated operation. An exported type or function creates a different obligation because consumers can build source against it.

The separation should therefore follow actual dependencies. Hiding a name is not an adequate explanation if consumers still need a property of the type it denotes. The interface has to contain enough information to support its promised use.

Abstraction still needs a runtime account

The catalogue describes the standard as requirements for implementations of C++. Those requirements establish program behavior while permitting implementation choices. The April draft's abstract-machine discussion makes this distinction explicit through observable behavior.

That model is relevant to the usual argument for C++ abstraction. A source-level interface does not prescribe a literal sequence of machine instructions. An implementation can transform the program while preserving the behavior required by the language.

Consequently, adding a module boundary does not itself imply a particular function-call cost. The generated code depends on the implementation and the program. A claim about runtime overhead needs evidence about the compiled case, rather than following automatically from the presence of import.

The same caution applies to promised build improvements. An explicit dependency can support a different compilation strategy, but the time spent by an actual build depends on its dependency structure and toolchain. The semantic benefit can be explained without claiming a universal timing result.

I regard the distinction as a strength when it stays explicit. The language can give a program a clearer interface structure while leaving compilers room to implement it efficiently. A library's design should state its contract in source terms and make performance claims at the level where evidence exists.

The published edition is a reference for precise questions

A proposal explains a design choice and its history. A working draft shows the evolving specification. The published edition identifies the international standard against which an implementation's claim can be framed. Those documents have related purposes, but they are not interchangeable.

This matters when an example from an early proposal differs from an implementation. The first question is whether the example describes the accepted form. A paper can contain alternatives or removed designs that remain useful historical evidence without being requirements.

The same discipline helps with diagnostics. An implementation can diagnose a source construct because it violates a rule or because the implementation lacks the required facility. Those explanations lead to different repairs. A precise edition and a precise facility give the investigation a useful starting point.

For the module example, the core relationship is straightforward. One translation unit declares the module interface and exports an operation. Another imports it and calls the exported function. Toolchain support must realize that relationship, and the build must provide its inputs in the required form.

A larger component adds complexity through its own exported types and dependencies. The standard cannot decide which of those belong in the public contract. It gives the author mechanisms with which to express that decision.

Publication is useful because those mechanisms now belong to an identifiable completed edition. C++20's module interface can be discussed as part of ISO/IEC 14882:2020, while a particular library can describe what it exports and which build combinations it supports. The next reader can then follow the relationship between the public declaration and the component that provides it.