C++ Modules
An important feature of C++20 is modules, a completely new way of organising source files. It aims to solve the problems of overly large translation units and repeated template instantiations caused by the traditional source‑file inclusion model, thereby speeding up compilation.
Module Units
A module unit is a translation unit, but not in the traditional sense of a translation unit that produces intermediate representation (IR) or machine code (binary). Instead, it is a new kind of module translation unit based on the C++ abstract machine.
A module unit consists of a global module fragment, a module declaration, a module implementation, and an optional private module fragment. The global module fragment and the private module fragment are optional:
If a module declaration does not have export, the file is a module implementation unit.
A module declared with export is a module interface unit; the rest are module implementation units. A module must have exactly one module interface unit. Module implementation units with the same name automatically obtain the declarations from the module interface unit.
Module names may contain dots (.). The dot has no special meaning, but by convention it indicates a hierarchical relationship.
Names beginning with std cannot be used as module names; they are reserved.
import module_name; imports a module, and export import module_name; causes translation units that import the current module to also import the dependency module A.
Note that import module_name; and module module_name; are also preprocessor directives, and they are processed before macro substitution and conditional compilation.
Exporting Content
Only declarations exported inside a module interface unit can be imported and used outside the module. The module interface unit determines the visibility of declarations.
Note that when export is applied to classes, unions, and enums, it distinguishes between declaration and definition. If a declaration appears in the module interface unit and the definition in a module implementation unit, the definition is visible but not reachable (see later).
If a declaration is exported by module A, it cannot be defined in module B, but specialisations are allowed in module B (provided A is imported).
Follow these principles:
- Only module interface units can export.
- Full template specialisations, partial specialisations, static assertions, and C++26
constevalblocks do not need to be exported. - If overloads exist, non‑exported overloads are not visible to external translation units, but are visible within the same module.
- For a
usingdeclaration that names functions, all associated overloads are exported.
Modules and Namespaces
Modules and namespaces are orthogonal; they are not alternatives. Namespaces can be used inside modules, and a namespace may have the same name as the module that contains it.
Exporting a namespace exports the declarations within it, and those declarations still belong to that namespace.
An unnamed namespace cannot be exported because declarations inside it have internal linkage. Similarly, a function or variable declared static cannot be exported, as those also have internal linkage.
Module Partitions
A module partition is a module unit. A module partition must be imported, directly or indirectly, by the primary module:
A module partition unit is also a module unit. All declarations and definitions inside a module partition are visible in the module unit that imports it, regardless of whether they are exported.
A module partition may be a module interface unit. To export a partition, it must be re‑exported by the primary module interface unit.
There can be at most one module partition with a given name.
Module partitions allow you to organise and extend a module, but declarations that need to be exported must be exported inside the module interface unit.
Global Module Fragment
The global module fragment serves to isolate definitions/declarations that do not belong to the current module.
Declarations indirectly declared in the global module fragment belong to the global module, not to the current module. The global module fragment together forms the global module.
In addition, sometimes you need global preprocessor directives for control, e.g., choosing different headers depending on the platform; you can also include them in the global module fragment.
Visibility and Reachability
If a name in a module translation unit is not exported, it is not visible to external translation units, and name lookup cannot find it.
If the definition of a class, union, or enumeration is in a module implementation unit and is not exported, its definition is not reachable; it cannot be used, and the type is considered incomplete.
Private Module Fragment
The private module fragment defines a module implementation area that is not a separate module unit, and it can only be accessed by the current module. A module may have at most one private module fragment.
The private module fragment is typically used to implement the entire module in a single file. It is similar to a module implementation unit, but resides in the module interface unit file.
Declarations and definitions inside the private module fragment are neither visible nor reachable.
extern "C++"
C++98 introduced extern "C" and extern "C++". For a long time, extern "C++" had no practical effect. C++20 repurposed it.
When a declaration marked extern "C++" is part of a module implementation, it belongs to the global module. This feature allows a library to be used both as a module and as a header file.
In Practice
In practice, the module interface unit plays the role of the former header file, and the module implementation unit plays the role of the source file.
For header‑only libraries or libraries that can be distributed in source form, it is generally unnecessary to distinguish between interface and implementation units.
If you need to export macros, you can isolate them in a separate header file and then #include it in the global module fragment.
When traditional headers are needed, they should also be included in the global module fragment.
How Modules Speed Up Compilation
Modules speed up compilation in four ways:
- Modules avoid repeatedly parsing source text into internal structural representations and performing syntactic analysis. This is similar to precompiled headers.
- Module units can be compiled independently into object files without repeated code generation. This is something precompiled headers cannot achieve.
- Modules no longer need
inlineto generate weak symbols for avoiding symbol conflicts, so the linker does not need to deduplicate weak symbols. - Modules can cache template instantiation results, saving the compiler from repeated instantiations and also reducing linker pressure.