C++20 Modules & Modern Build Systems: Replacing #include, CMake Integration, and 10x Faster Builds

C++20 Modules & Modern Build Systems: Replacing #include, CMake Integration, and 10x Faster Builds
Table of Contents
- The #include Problem: Why Builds Are Slow
- Module Anatomy: Export Module, import, and Ownership
- Module Partitions: Splitting Large Modules
- import std: The Standard Library Module (C++23)
- Header Units: Incremental Migration Path
- CMake 3.28+: Native Module Support
- Module Ownership and Linkage Rules
- Build Time Benchmarks: #include vs Modules
- Toolchain Support Matrix (2026)
- Frequently Asked Questions
- Key Takeaway
The #include Problem: Why Builds Are Slow
Problems with #include:
- Re-parse on every TU: Same headers parsed by every file that includes them
- Order sensitivity: Include guards work, but
#defineleaks across files - Macro pollution: Macros from one header infect all subsequent headers
- No encapsulation: All names (exported or not) pollute the global namespace
- Implicit dependencies: If file.cpp uses
std::string, but only includes via a chain of other headers, it compiles today but breaks tomorrow
Module Anatomy: Export Module, import, and Ownership
A module consists of one or more module units — translation units that declare themselves part of the module:
Key differences from #include:
import mathreads a pre-compiled Binary Module Interface (BMI/IFC) — not source text- Non-exported names are completely invisible to importers (true encapsulation)
- Macros defined inside the module do NOT leak to importers
- Multiple imports of the same module are instant (BMI cached)
Module Partitions: Splitting Large Modules
Large modules can be split into partitions — sub-units that belong to the same module:
import std: The Standard Library Module (C++23)
C++23 standardizes importing the entire standard library as a module:
Header Units: Incremental Migration Path
Header units let you import existing headers without converting them to modules:
CMake 3.28+: Native Module Support
msvc (Visual Studio) — .ixx files:
clang with Ninja (fastest):
Build Time Benchmarks: #include vs Modules
Toolchain Support Matrix (2026)
| Feature | MSVC 19.34+ | Clang 17+ | GCC 14+ |
|---|---|---|---|
| Basic export/import | ✅ | ✅ | ✅ |
| Module partitions | ✅ | ✅ | ✅ |
import std; | ✅ | ✅ | ⚠️ Partial |
| Header units | ✅ | ✅ | ✅ |
| CMake integration | ✅ | ✅ | ✅ |
Frequently Asked Questions
Can I mix modules and headers in the same project?
Yes — and this is the recommended migration strategy. You can #include legacy headers inside module interface units (the includes are private to the module), and importers never see the polluted namespace. Migrate module by module over time.
Do modules break ODR (One Definition Rule)? Modules actually improve ODR compliance. The compiler tracks module membership and rejects cases where the same entity is defined in multiple modules with different definitions. With headers, the same ODR violation causes undefined behavior silently.
Are module file extensions standardized?
No — .cppm (Clang), .ixx (MSVC), and .mpp are all common. CMake 3.28+ and the NDK use .cppm by convention. The extension doesn't matter — the export module declaration is what identifies a file as a module interface unit.
Key Takeaway
C++20 Modules are the most significant change to C++ code organization since the language was created. They eliminate the 50-year-old #include textual inclusion model and replace it with a compiled binary interface system that scales to millions of lines of code. The build time improvements (10-30×) are just the most visible benefit — true encapsulation (macros don't leak, internals are invisible) and improved ODR safety are equally important. In 2026, any new C++ project should default to modules with import std;.
Read next: Embedded C++ & Safety-Critical Engineering →
Part of the C++ Mastery Course — 30 modules from modern C++ basics to expert systems engineering.
