C++ Modules Clash: The Hidden Build System Challenge of import std
Share this article
The arrival of import std in C++26 promises to revolutionize dependency management, replacing error-prone header includes with streamlined modules. But as developers begin testing this future, a thorny build system conflict threatens to undermine progress. Recent hands-on experiments with GCC 15 expose a fundamental flaw: module caching collisions that break multi-target builds.
The Fragile Foundation
A simple helloworld test reveals the initial workflow:
g++-15 -std=c++26 -c -fmodules -fmodule-only -fsearch-include-path bits/std.cc
g++-15 -std=c++26 -fmodules standalone.cpp -o standalone
The first command builds the standard library module; the second consumes it. While functionally sound, quirks emerge immediately. The -fmodule-only flag requires -c to suppress linking—a counterintuitive dance. Worse, the compiled module lands in ./gcm.cache/, a hardcoded directory with no compiler override.
Build times hint at potential: 3 seconds for the std module, then 0.65s for the binary (versus ~1s for traditional #include). But this efficiency shatters when scaling beyond trivial examples.
The Ninja Nightmare
Modern build systems like Ninja compile from the root directory, meaning every target attempts to write to the same gcm.cache/std.gcm. Targets clobber each other, forcing build failures. Even if deduplicated, a brutal limitation remains: only one std module version can exist per build tree.
As the experiment notes:
"You can only ever have one standard library module across all build targets... People are doing weird things out there and they want to keep on weirding on."
This extends beyond std. Any shared module name (e.g., utils) would collide globally. Repositioning the cache via working directories isn't viable—it violates core build system invariants.
A Path Through the Maze
The proposed salvation? Target-private directories—a build system mechanism allocating isolated scopes per target. Compiler invocations might then look like:
--target-private-dir=${target_dir} --top-private-dir=${global_cache}
The top directory could cache shared artifacts like std.gcm, while per-target directories prevent clashes. Targets could safely reuse prebuilt modules when compatible, preserving speed without collisions.
Until this materializes, import std remains a solitary experiment—not a team player. For C++ modules to thrive, compilers and build systems must co-evolve. Otherwise, the promise of modules may stall in the maze of legacy workflows.
Source: Trying Out import std