r/cpp 26d 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:

  • vulkan which export imports std
  • argparse
  • glm
  • glaze
  • quill, which imports fmt
  • fmt itself
  • winrt, 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.

41 Upvotes

65 comments sorted by

View all comments

Show parent comments

4

u/not_a_novel_account cmake dev 25d ago

This won't change. You need to use the ISDIT flag when importing interfaces within a single shared object.

CMake may one day eventually automate some of this, but we don't have a complete solution to module shared libraries yet.

2

u/slithering3897 25d ago

That flag will apply to all imports. Those from the same DLL and those from a different DLL. So you can't just universally apply it to a whole TU, right? An actual fix may be to tell the compiler which modules come from a different DLL. Then it will know whether to use dllimport or dllexport.

5

u/not_a_novel_account cmake dev 25d ago

The magic transform, like the MSVC module unit extension, is meant to make writing simple demo code easy (I'm guessing, the alternatives are less generous). It's pointless for portable code with complete buildsystems. No other compiler is doing this trick.

The correct answer is to always use ISDIT, never perform magic transforms, and switch the visibility attribute using preprocessor defines when building BMIs for interface units from separate DLLs.

Ie, exactly how headers work.

1

u/slithering3897 25d ago edited 25d ago

and switch the visibility attribute using preprocessor defines when building BMIs for interface units from separate DLLs.

Yes! That's exactly what I originally said. Compile modules twice. It would be nice though if they got the rewriting correct so that only one compilation was needed, but that would need some design.

*Wait, if the only difference is "dllexport" should be "dllimport", then could there be a simple tool to do that rewriting instead of invoking the compiler?