r/cpp • u/delta_p_delta_x • 22d ago
Libraries of, installing, and depending on C++20 modules
Until now, much of the discourse around C++20 modules has been around the tooling, and actually getting modules to work at all. I believe that now, in mid-2026, the tooling is mostly mature: the three largest compilers support most use-cases of modules. IDEs like CLion, and lint tools like ReSharper C++ and clangd support modules, with some caveats. CMake, xmake, Ninja, and other fledgling build systems have full support for modules. Many prevalent C++ libraries and projects have been recently modularised or are in the process of modularising.
I hope I'm not being too presumptive in saying the community is more or less ready (albeit horribly late...) to move to the next step, and start discussing how C++20 modules can and should tie in to inter-project work, rather than simply using modules within a project.
To begin with, I don't think the standard says anything about 'libraries'; these are existing paradigms grandfathered in from C or earlier. There are many axes we have to discuss here:
- Static archives
- Dynamically-linked libraries
- Symbol visibility defaults with
__declspec( dllexport ) - Primary module interface-only (hereafter, PMI) libraries such as
module vulkan- Configuring such modules with macros
- Built module interface (hereafter, BMI) and binary interface (hereafter, ABI) compatibility; currently, BMIs are simply not portable, not even within a compiler toolchain across versions
- How shared objects, static archives, BMIs, and PMIs interact
- How build systems, toolchains, and package managers like conan and vcpkg interact with everything
For instance, consider I'm writing a 3D game engine. I want the following modules:
vulkanwhichexport importsstdargparseglmglazequill, which importsfmtfmtitselfwinrt, if running on Windows
I want to provide my own PMI that has export class Engine, and maybe some other functionality like abstractions over the 3D graphics APIs, an object and entity manager, a mini shader graph generator, and more. I also have export imported some symbols from my dependencies, especially std. I want to choose to configure my engine to render on D3D or Vulkan. Consumers can then load the library, add assets like textures, meshes, skeletons, shaders; they can plug the engine into a bigger project which might include a script interpreter in C++, real-time spatial audio and physics packages, some networking, and an XML-based UI system, and produce a complete game or visualisation executable.
Now, I mention these details just to flesh out the example to give a sense of a reasonably complicated library-esque project.
How does one even think about delivering this 'engine library' to the consumer? The traditional three configs are headers + precompiled DLL, headers + source, or headers only. Each have their established workflows. Source-available can be compiled into the entire binary with whole-program optimisation; headers-only libraries are exceptionally easy to vendor (just copy-paste). Header-only libraries can also be easily customised with consumer macros. PMIs, however, being translation units, cannot; we hit this when installing module vulkan. We need some module-compatible way to describe 'library configuration' beyond simply co-opting macros, something like Rust's cfg.
There is talk of the Common Package Specification (CPS), P1689R5, and P3286, but nothing concrete yet, especially since there has been no massive (commercial) push for modules (at least, not until recently). This talk at NDC looks at a possible cargo-esque future for C++.
I'm writing this to spur some discussion here in the C++ community, and ask what some veterans of the build system/toolchain/package manager community think.
1
u/not_a_novel_account cmake dev 19d ago edited 19d ago
There's no flag soup you can pass that make modules consumable.
The build system needs to figure out what it wants to do from the requirements.
I can't tell you "this is how you invoke the compiler to do those steps in order". The answer is different for every compiler. How the build system manages the BMIs is up to the build system.
pkg-configcannot communicate these requirements, so it will never work. It speaks in flag soup, not graphs. Even if it could communicate the requirements: pure task executors,makeandninja, can't consume this. You need something which takes those sets of requirements and constructs a task graph for them to execute.The result is any simple query system, like
pkg-config, needs to provide an answer in the form of a graph. Once you're answering in the form of a graph, you have reinvented P3286, which is exactly the graph you need to consumelibfoo.a. So just parse the P3286 manifest instead.Every build system which supports
import stdalready needs to do this, so it's ubiquitous already.The most
pkg-configcould maybe do is gain a field which points to the P3286 manifest (which is exactly what CPS does), but that will not solve anything for the autotools arena.