C++ Modules (C++20)
- Description: A note on C++20 modules —
import,export, module partitions, header units, the global module fragment, and migration from#include - My Notion Note ID: K2A-B1-27
- Created: 2022-03-15
- Updated: 2026-02-28
- License: Reuse is very welcome. Please credit Yu Zhang and link back to the original on yuzhang.io
Table of Contents
- 1. Why Modules?
- 2. Importing a Module
- 3. Defining a Module
- 4. Module Partitions
- 5. Header Units
- 6. Global Module Fragment
- 7. Migration from
#include - 8. Compiler and Build-System Support
1. Why Modules?
#includepastes header text into every TU. Large project → same headers reparsed thousands of times; macros can leak.
Modules fix both:
- One-time compilation — module parsed once, imported as BMI (Binary Module Interface).
- Hard isolation — macros, internal symbols, unrelated decls don't leak across module boundary.
- Faster builds — no per-TU re-parse.
- Cleaner ABI control — only
exported symbols visible to importers.
- Not a runtime feature — changes build model, not generated code.
2. Importing a Module
import std; // C++23: the entire standard library as one module
import std.compat; // C++23: same as std plus C library names in the global namespace
import math; // your own module
import :helpers; // a module partition (only inside another module)
importlooks like a statement, but must appear at top of file (after global module fragment if any).- Doesn't introduce names into current scope — only names exported by the module.
3. Defining a Module
- Primary interface unit declares the module + what it exports:
// math.cppm (the .cppm extension is convention; not required)
export module math; // primary interface
export int add(int a, int b) { // exported, visible to importers
return a + b;
}
int internal(int x) { // NOT exported; internal to this TU
return x * 2;
}
export { // export multiple declarations at once
int sub(int a, int b);
int mul(int a, int b);
}
- Implementation files (no
exporton module decl) provide bodies:
// math_impl.cpp
module math; // implementation unit (no `export`)
int sub(int a, int b) { return a - b; }
int mul(int a, int b) { return a * b; }
4. Module Partitions
- Large modules split into partitions — submodules sharing the parent's name.
// math-trig.cppm
export module math:trig; // partition of math
export double sin(double x);
export double cos(double x);
// math.cppm
export module math;
export import :trig; // re-export the partition
- Importers see one logical module:
import math;
math::sin(0.5);
- Partitions can also be private (not re-exported):
// math.cppm
export module math;
import :impl_helpers; // not re-exported; for use only inside math
5. Header Units
- Import an existing header as if it were a module. Useful during migration.
import <vector>; // standard header as a module
import "myheader.h"; // your header as a module
- Header units preserve macros (named modules don't), but compile faster than
#include. - Stepping stone, not final destination.
6. Global Module Fragment
- Module needs
#include-only header (un-modularized) → global module fragment at top:
module; // begin global module fragment
#include <vector> // legacy header
#include "legacy_macro.h" // bring in macros, types, etc.
export module myapp; // begin the module proper
import std; // can also use proper imports
export void foo(std::vector<int>);
- Holdover for legacy interop. New code → prefer
importandimport <header>;.
7. Migration from #include
Practical migration path:
- Convert heaviest, most-included headers first (broad include-graph). Header units (
import <header>;) → easy win. - Modularize internal libraries as named modules.
#include "internal.h"→import internal;. - Public APIs last — more visible interface + ABI.
- Don't mix lifestyles — once a header is a module, consumers should
import.
- Module-aware compilers consume
#includefor unported headers → no flag day needed.
8. Compiler and Build-System Support
As of late 2025:
- MSVC — strongest. Named modules with
/std:c++20.import std;needs/std:c++latest. - Clang — named modules solid since Clang 16;
import std;since Clang 18 (libc++ configured to ship std module). - GCC — improving;
import std;+ advanced features lag others. - CMake — named-module support in 3.28 (
CMAKE_CXX_SCAN_FOR_MODULES);import std;in 3.30. - Bazel, Meson, etc. — separate module integrations; check vendor docs.
- Reality check: real build-time wins, toolchain still maturing. Most codebases still on
#include. Expect to mix both for a while.