Introduction and Overview
C++ modules, introduced in C++20, have evolved significantly by August 2025, but their adoption and implementation vary across compilers and build systems.
C++ modules aim to replace the traditional header file system, offering faster compilation, better encapsulation, and improved dependency management. They consist of module interface units (exporting declarations) and implementation units, with named modules and header imports as key features.
Despite their standardization in C++20, practical use is still maturing due to complex tooling and ecosystem challenges.
Compiler Support
As of Clang 19 (released September 2024), modules are well-supported for both named modules and header imports. Clang's implementation is considered the most mature, with robust support for module compilation and linking. Recent improvements include better handling of global module fragments and standard library modules (e.g., std module).
GCC 15 (expected 2025) has experimental support for modules, but it's less complete than Clang. Named modules work, but issues persist with standard library modularization and cross-translation-unit dependencies. Full support is still in progress.
Microsoft Visual C++ (MSVC) supports modules in Visual Studio 2022 (17.10 and later). It handles named modules and header imports but requires specific project configurations. MSVC's integration with build systems is improving, though it lags behind Clang in some edge cases.
Standard Library Modules
The C++20 standard library is partially modularized. The std module (covering the entire standard library) and submodules like std.compat are available in Clang and partially in MSVC. GCC's support for standard library modules is incomplete, with ongoing work to stabilize it.
Compilers are moving toward precompiled standard library modules to reduce compilation times, but this is not yet universal.
Build System Support
CMake 3.28+ (2023) supports C++ modules via CXX_MODULES and FILE_SET features. However, dependency scanning and module caching are still evolving, and cross-compiler compatibility remains a challenge.
Bazel has experimental module support, but it's not production-ready for complex projects.
Tools like Ninja and Meson are adapting, but module-specific workflows (e.g., dependency scanning for BMI—Binary Module Interface—files) are not fully streamlined.
Adoption
Modules are today primarily used in new or experimental projects. Large codebases (e.g., game engines, legacy systems) often stick to headers due to migration costs and incomplete tooling.
Some open-source projects and early adopters (e.g., in game development or high-performance computing) report 30-50% compilation speed improvements with modules, but these require significant refactoring.
Community Feedback
The C++ community has voiced several complaints about C++ modules, despite their potential to improve compilation speed and code organization. These issues, based on discussions in forums, blogs, and recent social media posts, stem from practical challenges in adoption and implementation as of August 2025.
A Stack Overflow comment linked in a Reddit thread (from r/cpp, referenced in a 2023 discussion) states, “Currently there is no support for Makefile generation… As this, it is more or less unusable in the moment or you want to write down all your dependencies by hand... nightmare!”
A Reddit post on r/cpp discusses an MSVC C++20 compiler bug with modules and non-exported classes, noting, “The C++ module implementation of MSVC is pretty impressive. I've (successfully) spent now countless hours converting our sources in this project from using headers to modules.” However, the post highlights a bug with non-exported classes, causing compilation errors.
A Reddit thread referenced in a Stack Overflow post discusses modules’ purpose, with a user commenting, “Modules are an alternative to the traditional header+source file structure. When to use - never, until they gain widespread support in compilers. Then - always (except some cases where they can't be used, e.g. to import macros from a different file).”
An r/cpp thread about C++20 modules and Boost notes, “The project makes extensive use of C++20 modules and encapsulates both C++ standard library and third-party libraries. While we also did experiment with modules partition in some parts of the source code, we didn’t find significant benefits and so didn’t employ it extensively.”
The same r/cpp thread on C++20 modules notes that module partitions (like those in our math_ops example) “didn’t find significant benefits”.
Below are the main community complaints.
Build systems like CMake, Bazel, and Ninja struggle with module dependency scanning and Binary Module Interface (BMI) generation, requiring intricate configurations. Developers report that setting up modules in large projects is cumbersome due to inconsistent dependency management across compilers (Clang, GCC, MSVC). CMake’s CXX_MODULES
support (introduced in 3.28) is a step forward but still feels experimental, with slow scanning and poor cross-platform portability.
While Clang leads in module support, GCC and MSVC lag, leading to inconsistent behavior across platforms. GCC’s module support in GCC 15 (2025) remains experimental, with issues in handling standard library modules and cross-translation-unit dependencies. MSVC requires specific project settings, and its module support is less robust for complex cases. This fragmentation forces developers to avoid modules in cross-platform projects.
The C++ standard library’s modularization (e.g., std module) is incomplete or poorly supported. While Clang supports std and std.compat modules, GCC’s implementation is partial, and MSVC’s is limited. Developers expected precompiled standard library modules to reduce compile times, but these are not universally available, diminishing the benefits of modules.
Refactoring legacy code to use modules is time-consuming and error-prone. Converting headers to module interfaces requires rethinking visibility (via export), handling global module fragments, and ensuring compatibility with non-module code. Large projects with thousands of headers face significant hurdles, and the lack of automated migration tools exacerbates the issue.
Module syntax and semantics (e.g., export, import, module partitions) are confusing and poorly documented. Developers find the rules for module visibility, reachability, and linkage unintuitive compared to headers. The interaction between modules and traditional headers (e.g., header imports) adds complexity, especially for mixed codebases. Community feedback on social media highlights confusion over module partitions and the need for better tutorials, with some calling modules “overengineered.”
Third-party libraries and frameworks rarely support modules, forcing developers to mix modules with headers. Popular libraries (e.g., Boost, Qt) are still header-based, and their slow adoption of modules limits the benefits of modular codebases. This creates a dependency bottleneck, as projects must wait for the ecosystem to catch up.
The promised compilation speed improvements are inconsistent or overstated. While modules can reduce compilation times (30-50% in some cases), this depends on the project and tooling. Dependency scanning and BMI generation can introduce overhead, especially in poorly optimized build systems. Some developers report negligible gains after significant refactoring. Discussions on social media mention cases where modules slowed builds due to scanner inefficiencies or compiler bugs.
Module-related errors are cryptic and hard to debug. Compilers often produce vague error messages for module-specific issues (e.g., missing exports, cyclic dependencies). This frustrates developers, especially those new to modules, as debugging requires deep knowledge of module mechanics.
The C++ community values the potential of modules but is frustrated by immature tooling, inconsistent compiler support, and high adoption costs. The steep learning curve, limited ecosystem support, and underwhelming performance gains in some cases further fuel skepticism. While Clang and CMake are making strides, many developers feel modules are not yet ready for widespread production use, particularly in large or legacy projects.
Examples
To illustrate why C++ modules can be tricky, I'll provide first one simple example that demonstrates common pitfalls, such as module visibility, export/import semantics, and issues with mixing modules and headers. Then I’ll provide another one that highlights cyclic dependency risks, partition complexity, inconsistent standard library support, and build system challenges.
Use Clang 19 or MSVC (Visual Studio 2022 17.10+). GCC 15 may work but is less reliable. Use CMake 3.28+ with the provided CMakeLists.txt. Run cmake -S . -B build and cmake --build build. Commenting out the problematic lines in main.cpp (e.g., subtract or Point) will make it compile. Leaving them in will trigger errors, demonstrating visibility issues.
First Example
The example includes a modularized library and a main program, highlighting problems like missing exports, cyclic dependencies, and build system challenges. Explanations of the tricky aspects are embedded in comments within the code and summarized afterward.
The CMakeLists.txt shows the need for module-specific flags (e.g., -fmodules for Clang) and experimental CMake features. Dependency scanning for modules is slow and error-prone, especially in large projects. Different compilers require different configurations, making cross-platform builds tricky. To be on the safe side, use a modern build system (CMake 3.28+) and test configurations thoroughly.
In mathlib.cpp, the subtract function and Point struct are not exported. This causes compilation errors in main.cpp when trying to access them, as modules enforce strict visibility. Developers often forget to export necessary entities, leading to cryptic errors like "symbol not found."
To get around this error, explicitly export subtract and Point with export int subtract(int, int);
and export struct Point;
.
A second problem in this code: including <vector>
in mathlib.cpp can cause issues because headers are not inherently modular. If main.cpp tries to use std::vector, it may fail in strict module mode (e.g., Clang’s -fmodules-strict) since <vector>
isn’t imported as a module. The global module fragment (module : global;) for <iostream> is a workaround but adds complexity. Use import <vector>;
if the compiler supports standard library modules, or avoid headers in module interfaces.
The use of import
in main.cpp assumes the compiler supports standard library modules. If not (e.g., GCC 15’s incomplete support), you’d need to fall back to #include
, breaking module purity and causing potential conflicts.
The syntax for export, import, and global module fragments is unfamiliar to developers used to headers. Errors (e.g., missing exports) produce confusing messages like “entity not declared in module purview,” which are hard to debug without deep module knowledge.
Second Example
To further illustrate why C++ modules can be tricky, I'll provide another example that highlights additional common issues. This example highlights cyclic dependency risks, partition complexity, inconsistent standard library support, and build system challenges - key community complaints about C++ modules.
These issues make modules tricky due to their strict semantics, immature tooling, and compiler variability. While modules promise better encapsulation and compilation speed, these practical hurdles slow adoption, especially in complex projects.
These are frequent pain points raised in the C++ community, complementing the previous example's focus on export visibility and header interactions. The example includes two modules, a main program, and a build configuration, with comments explaining the tricky aspects.
Note that the CMakeLists.txt requires explicit module source declarations and compiler-specific flags (e.g., -fmodules for Clang, /experimental:module for MSVC). Module partitions complicate dependency scanning, as build systems may not correctly resolve math_ops:basic and math_ops:impl.
Incorrect dependency scanning can lead to missing Binary Module Interface (BMI) files or out-of-order compilation, causing build failures. CMake might fail with: "cannot find module math_ops:basic BMI." This is a common frustration in community discussions, as CMake’s module support is still experimental.
Problem: in math_utils.cpp (below), print_complex
fails because Complex
is in the non-exported math_ops:impl
partition (above).
The math_ops module uses partitions (math_ops:basic
and math_ops:impl
) to organize code. However, partitions require explicit imports (e.g., math_ops:basic in math_utils.cpp), and non-exported entities in implementation partitions (like Complex) are inaccessible, causing errors in math_utils.cpp and main2.cpp (below).
Partitions are powerful for organizing large modules but introduce verbose syntax and strict visibility rules. Developers often struggle with the distinction between interface and implementation partitions, leading to errors like "entity not found in module purview."
In math_utils.cpp, the import math_ops
; creates a potential cyclic dependency if math_ops were to import math_utils. If math_ops imported math_utils, Clang might report: "error: cyclic module dependency between math_ops and math_utils."
While this example avoids a direct cycle, real-world projects often encounter cycles when modules mutually depend on each other (e.g., a utility module needing types from a core module and vice versa).
Cyclic dependencies cause compilation failures with vague errors (e.g., "module not found" or "cyclic dependency detected"). Resolving them requires restructuring code, often by introducing a third module or using forward declarations, which adds complexity.
Another issue: with GCC, you might see: "error: module 'std.vector' not found." bechase the import <vector>; in math_utils.cpp assumes the compiler supports standard library modules.
As of August 2025, Clang 19 supports this, but GCC 15 has partial support, and MSVC’s support is inconsistent. If the compiler doesn’t support import <vector>, the build fails or requires falling back to #include <vector>, breaking module purity.
Developers must check compiler-specific support for standard library modules, leading to portability issues. This is a frequent complaint on social media, where users note GCC’s lag in standard library modularization.
Errors like those caused by accessing Complex in main2.cpp or math_utils.cpp produce cryptic messages (e.g., "Complex not declared in module math_ops"). This is due to modules’ strict encapsulation, which hides non-exported entities.
Debugging module-related errors requires understanding module purview and export rules, which many developers find unintuitive. Social media posts frequently cite "terrible error messages" as a major hurdle.
Future Outlook
C++23 refines modules with features like explicit import/export control and better integration with header imports. C++26 (expected 2026) is likely to address remaining pain points, such as standard library modularization and build system integration.
The community expects broader adoption as tools like CMake and Bazel mature and compilers align on implementation details.
While some on social media are skeptical about the full adoption of C++ modules, few categorically believe they will never be adopted. The dominant view is that modules face significant hurdles—ecosystem immaturity, tooling inconsistencies, migration costs, and issues like cyclic dependencies—that make widespread adoption unlikely in the near term (5-10 years). However, optimism persists among early adopters, who see modules gaining ground as compilers (e.g., Clang 19, MSVC) and build systems (e.g., CMake 3.28+) mature. Headers are expected to coexist with modules, especially for legacy code, rather than being fully replaced.
For now, my honest opinion about modules is: be conservative, start using for personal projects but stay away with professional use until it matures enough.