Module Deep Dive
Now that we’ve seen the basics of modules and some code to provide context, let’s look deeper into them.
Using a Module in Code
Using a module is simple, as we’ve seen earlier. It only requires an import declaration.
1 ```
2 import Aegean;
3 ```
The declaration brings the module Aegean into file scope, providing access to anything the module exports. The code using a module is an importer.
Module Segments and Fragements
Here’s a table listing the keywords, segments, and fragments of a Module Interface Unit and Module Implementation Unit.
| Module Interface Unit | Module Implementation Unit |
|---|---|
| module; | module; |
| global module fragment | global module fragment |
| (preprocessor commands only) | (preprocessor commands only) |
| (export) import module name(:partition) | import module name(:partition) |
| module purview | module purview |
| export module name(:partition); | module name(:partition); |
| export… | export… |
| (implementation code) | (implementation code) |
| module :private | |
| private module fragment | |
| (implementation code) |
Each column lists the keywords and sections used in a Module Interface Unit (MIU) and a Module Implementation Unit. Let’s look at them in detail, and revisit those we’ve discussed before.
Module Naming
A module unit name and partition name follow the same rules as any other name in C++, except that a period may appear as a separator for readability. The period has no semantic purpose.
A colon separates the module unit and partition names. An implementation translation unit always uses just the module name, even when it is a partition. In a following section, we’ll see when only the partition name is necessary and when both names are required.
module; Starts the Global Module Fragment
The module; is an optional declaration that indicates this is a module. It must be the first line of code in the source, with only comments allowed before it. This declaration may be used in any module translation unit.
The purpose of module; is to begin the global module fragment, which ends at the export module or module declarations discussed in the next section. This fragment allows only pre-processor statements, primarily header inclusion statements. If a global module fragment is not needed, the module; declaration may be omitted.
![]() |
Compilers may allow code other than precompiler statements to be present, but this is non-standard. As compilers comply with the standard, they may report errors in the future in place of warnings that some now emit. |
Declarations (export) module name(:partition);
The export module name(:partition); and module name(:partition) declarations create a named module or module partition unit. They are used in the MIU and implementation module units, respectively. These declarations end the global module fragment and begin the module unit purview, which includes everything to the end of the translation unit.
The export version places the module in the global namespace, making it available to importers. The implementation version places the translation unit in the module. Here are examples of the primary MIU, non-partition implementation translation units, partition MIU, and partition implementation. Partitions are discussed in Partitions and Submodules
- Primary MIU:
export module Javanese; - Implementation:
module Javanese; - Partition MIU:
export module Javanese:Balinese; - Partition Implementation:
Module implementation: module Javanese;
Declarations (export) import (module.name)(:partition)
The (export) import (module name)(:partition) and import (module.name)(:partition) declarations are used in the MIU and unit implementation files, respectively, to import modules into their namespace. The same statement allows importers to access a module. The export version exposes the module to the global namespace. The names must be the same as the name of the target module.
The export import declaration is used in an MIU to import a partition into the MIU and export it to other translation units. An MIU exports a partition using export import :partition; without the need to specify the module name.
1 `export import :Balinese; // exports all from :Balinese partition`
export… or How Can I Export Thee? Let Me Count the Ways
Listing what can’t be exported from a module would be easier, but I’ll list both. The book provides examples and further discussion of many allowed exports. We’ll start with what can’t be exported.
What Cannot Be Exported?
- Macros (
#define) (because they belong to the preprocessor) - Preprocessor Directives (
#pragma once,#include) - Anonymous Namespaces
Exportable Entities in C++ Modules
- Functions
- Regular functions
constexprfunctionsinlinefunctions
- Classes and Structs
- Classes
- Structs
- Member functions
- Static member variables
- Variables
constvariablesconstexprvariablesinlinevariables
- Enumerations
enumenum class
- Type Aliases
using(type alias)typedef
- Templates
- Function templates
- Class templates
- Variable templates
- Alias templates
- Concepts
conceptdefinitions
- Namespaces
- Entire named namespaces
- Specific members inside a named namespace
- Neither anonymous namespaces nor their content can be exported
Declaration module :private
This statement only applies to the primary MIU. It is a private fragment containing the implementation code for the module. A module unit with a private module fragment must be the only module unit in the module. This code does not change the Built Module Interface (BMI), so modifications do not require recompilation of the user code; only relinking is needed. It is hidden and may not be exported. [Rud - need to check some of these points with code.]
![]() |
Neither GCC 14,2, Clang 19/20, nor MSVS 17.9 support private fragments. [Rud - try to test MS with godbolt.] A workaround is to create a module partition and not export it to the global namespace. |
Module Linkage
Modules introduce new linkage definitions to support the encapsulation provided by modules.
Pre-Module Linkage
Before C++20 provided modules, two forms of linkage were defined: internal and external.
- Internal Linkage
-
Symbols are limited to the translation unit where they are declared, a form of implementation hiding since other translation units cannot access them. These symbols are declared
staticor in an anonymous namespace. - External Linkage
-
Non-static functions and non-const global symbols are accessible by other translation units. External linkage is problematic because symbols in multiple translation units can collide, causing a linker error. Symbols also leak into the global scope, which may not be desirable.
C++20 Module Linkage
The main change is the requirement to use export to expose any symbols in a module to other translation units, which must, in turn, explicitly access them by importing the module. Within a module, non-exported symbols have module linkage so any translation unit in the module may access them.
Module linkage improves encapsulation by hiding non-exported symbols. This linkage also reduces linking errors from name duplication. Use static and anonymous namespaces only when isolation from other translation units in the same module is necessary.“
- Global Module
-
The global module bridges existing C++ code and modules. This module contains all the symbols in global module fragments. The pre-module internal and external linkage rules apply. You can’t declare symbols directly in a global module fragment, but they can be introduced by including a header file. Should the header contain
staticor anonymous namespaces, they are available to the translation unit. Code in a global module fragment is identical to code in a non-module translation unit.
The global module and global scope are nearly identical, and pragmatically, they are. The only distinction is that static and anonymous namespaces are considered part of the global module despite not being in the global scope.
