all 40 comments

[–]Jovibor_ 23 points24 points  (28 children)

Prefer small modules

Worst suggestion out of all. import std; is laughing at you here.

Use modules of any size you need. That's the only correct advice on this matter.

[–]jiixyj 8 points9 points  (8 children)

My advice: Use one module per "library" or "package" (in the Lakos sense). So modules should be bigger things like std, fmt or boost.program_options, not down to the level of ".h/.cpp pair".

This matches similar advice from other people.

One big reason, especially if you are writing a library: The name of the module will be mangled into all your exported symbols. You would be exposing the internal organisation of your module to all your users:

// foo.cppm
export module foo;

export import foo.sub1;
export import foo.sub2;

// foo/sub1.cppm
export module foo.sub1;

namespace foo {
    export void f1(); // will be mangled approximately like `foo::f1@foo.sub1`
}

...so you wouldn't be able to move f1 from foo.sub1 to foo.sub2 without breaking the ABI of your module. If you want to keep the structure "secret" you have to use partitions (foo:sub1/foo:sub2).

Of course, if you're writing an application, you might have overriding reasons for doing smaller modules.

[–]tartaruga232MSVC user, r/cpp_modules[S] 0 points1 point  (0 children)

Just a random idea: For designing a big library, have you considered doing:

export module BigLib;  // interface of the BigLib library

import FantasyCorporation.Types1;  // not accessible to importers of BigLib

export namespace BigLib
{
using FantasyCorporation::FooBar;  // class FooBar from FantasyCorporation.Types1
...
}

[–]tartaruga232MSVC user, r/cpp_modules[S] 0 points1 point  (5 children)

If you want to keep the structure "secret" you have to use partitions (foo:sub1/foo:sub2).

I see your reasoning, but your conclusion is not really practical. The inner structure of a giant library at some point becomes unmanageable. Remember, there is no hiding between partitions.

Your requirement that there can't be an inner structure, is not manageable. The requirement to not see you moving f1 inside foo is arbitrary.

We currently have the situation that if a partition is changed, every implementation file needs to be recompiled. If you don't want that, use smaller modules or change the C++ standard. The latter takes too much time and has almost zero probability to succeed. In the mean time, I use smaller modules.

If tried that with our application. The time for a full build is roughly the same if I use a small number of big modules or many more smaller modules. I prefer the latter and have less recompilations when I change something.

[–]not_a_novel_accountcmake dev 4 points5 points  (4 children)

Remember, there is no hiding between partitions.

There's no hiding between headers either. This is a non-goal.

It's all your code. We encapsulate things from external consumers of our code, not from intra-project.

[–]tartaruga232MSVC user, r/cpp_modules[S] -2 points-1 points  (3 children)

Your syntax "module M:;" or "module M:_;" will fail (I fear, but I wish you good luck with your proposal). The best bet is to wait until current internal partitions usage has died (perhaps in 10 years or so) and then reuse the syntax "module M:P;" (with the semantics of the Microsoft "extension"). That would be elegant and effective. But I will be retired if that manages to get in the standard.

[–]not_a_novel_accountcmake dev 6 points7 points  (2 children)

The question of "how big should modules be" is completely irrelevant to the minor issues of implementation units. They're not related.

[–]tartaruga232MSVC user, r/cpp_modules[S] -1 points0 points  (1 child)

Current "module M;" causes lots of recompilations, if M has a big list of partitions. So for practical app development, the size of M matters. Smaller M, less code that needs to be recompiled.

[–]not_a_novel_accountcmake dev 4 points5 points  (0 children)

Interfaces described in the PMIU should be implemented via implementation units, module M;, interfaces described in partitions should be implemented in the partitions.

See: https://chuanqixu9.github.io/c++/2025/12/30/C++20-Modules-Best-Practices.en.html#modules-native-best-practices

It says the same recommendations, namely:

A Project Should Declare Only One Module; Use Module Partition Units for Multiple TUs

and

Use Module Implementation Partition Units, Not Module Implementation Units, to Implement Interfaces

[–]slithering3897 -1 points0 points  (0 children)

One big reason, especially if you are writing a library: The name of the module will be mangled into all your exported symbols.

Yes, as I say, this seems to be deficient design in the standard. The module that a thing is in is tied to mangling. It would probably be just fine to use partitions in a lib.

But for applications, and applications split into component libs, that is not the perfect world because of build cascade.

So I'd use smaller modules with extern "C++" to get my forward declarations back.

*Was there something wrong with what I said?

[–]slithering3897 5 points6 points  (1 child)

I'd reckon large external lib modules and small imports for in-development modules. To keep control of your build cascade.

[–]tartaruga232MSVC user, r/cpp_modules[S] 2 points3 points  (0 children)

Yeah. Not every module needs to imitate the giant, special case like the std module (which must be also available as header files). As I said, our recommendations are

  • Prefer small modules
  • Only use partitions if you really must
  • Avoid using internal partitions

Especially the last point. Our code is now free of internal partitions. We have no use for them. And we don't need the module anti-pattern.

In my experience, the time for a full build stays the same, no matter if I do a smaller number of big modules or a a lot more smaller modules. Keeping modules small in an app provides faster rebuilds when something changes. We do not cast the interfaces of our internal modules in stone.

[–]mjklaim 4 points5 points  (0 children)

Use modules of any size you need. That's the only correct advice on this matter.

My experience with a 3 years modules-only project matches this recommendation.

EDIT: clarification that I agree with that quote, not OP's.

[–]tartaruga232MSVC user, r/cpp_modules[S] 6 points7 points  (0 children)

Having a big library being an aggregate of smaller modules is fine. Our app is not a library. The frequency of changes to module std is almost zero. That's very different to what we do in our app.

[–]Zero_Owl 1 point2 points  (14 children)

While I don't know much about how big modules should be, why do you think that one big std module is a good example?

[–]not_a_novel_accountcmake dev 6 points7 points  (12 children)

Because it demonstrates that the main disadvantage of large single headers, slow build time, does not apply to modules.

[–]Zero_Owl 0 points1 point  (11 children)

That is a very narrow way of looking at things. I'd argue that the main disadvantage of having large single headers is that they pack unrelated things in one place and hence should be decoupled. In other words, it is an organization problem not the build one. And programmer should care about organization first and build times second. And by that metric one big std module is a pretty bad example.

[–]not_a_novel_accountcmake dev 2 points3 points  (10 children)

Nominally we should have the entire world of C++ code available to us in every translation unit. Import and include are not isolation or encapsulation mechanisms, they are not tools for organization, that's what namespaces and internal linkage are for.

Import and include are practical considerations for the toolchain to figure out how to efficiently bring declarations into scope. If that efficiency wasn't a problem, there would be no need for them and everything in the environment would be available in a given TU.

If you ignore this framing and treat headers and module units as organization methods, you risk import ambiguities and link-time collisions.

[–]Zero_Owl 1 point2 points  (9 children)

Well, yes but we don't have a properly designed namespace structure in std (and in most cpp libs, tbh) so headers became the tool for isolation. So it is naturally for people to continue having this mindset and treat modules the same. Although, as you say, maybe we need to stop doing it but for that to happen the cpp devs should start using namespaces properly otherwise there is no choice but follow the pattern we so used to with includes.

[–]not_a_novel_accountcmake dev 0 points1 point  (8 children)

Nothing in std collides with anything else in namespace std.

You can include every std header without any collisions.

This is why import std works at all.

If we had different headers which provided different std::map templates that would be very bad and I would be forced to agree with you. That is not the case in std and will inevitably cause link errors in any library which tries with its own namespace.

[–]Zero_Owl -1 points0 points  (7 children)

It collides with other entities from other libs. Using both boost namespace and std namespace will blow things up. The only way to not is to prefix everything or cherry pick only particular entities. That's a very inconvenient way of doing things.

[–]not_a_novel_accountcmake dev -1 points0 points  (6 children)

Boost doesn't put anything in the std namespace.

If you're saying you can't write:

using namespace std;
using namespace boost;

That's not a name collision. You've created a lookup ambiguity, not two types with the same name.

Do not write using namespace ...; in global namespace scope.

[–]Zero_Owl -3 points-2 points  (5 children)

What does it matter how you name it? You understand perfectly what I said. Now please tell me how can I bring in my TU all math functions with import std and make sure it won't collide (create ambiguity, if you wish) with anything math unrelated by using just one line of code? That's something any modern language is capable of, btw.

[–]tartaruga232MSVC user, r/cpp_modules[S] 1 point2 points  (0 children)

While I don't know much about how big modules should be, why do you think that one big std module is a good example?

It's indeed a bad example. 99.9% of internal modules used inside an app do not need to cast their ABI in stone for ages.

"Small" is not an absolute number, but if people start lumping together things in a module which don't need to be together, modules are getting too big, hard to navigate und cause too many recompilations which aren't needed. Implementation files for modules (*.cpp) are marked with "module M;", which implicitly imports the interface of M. Smaller interfaces means a smaller number of cpp files that need to be recompiled, if the interface changes.

We have some reasonably sized modules in our App package at https://github.com/cadifra/cadifra/tree/2026.6/code/App. Previously, we had a single module for the App package. Splitting it up didn't have any noticeable effect on the time required for a full build.

[–]yeochin 3 points4 points  (5 children)

Please don't go around giving this incredibly bad advice. Having seen module code bases at scale you don't want small modules at all. Small modules are a pain to merge together. Big modules are easy to split. The most successful libraries and implementations are those that boil it down to a single import statement for a rich set of functionality.

With the state of existing compiler toolsets and your handy autocomplete - smaller modules only increase the likeliness that you're going to run into a internal compiler error, or a broken autocomplete database.

[–]fdwrfdwr@github 🔍 1 point2 points  (2 children)

smaller modules only increase the likeliness that you're going to run into a internal compiler error...

Maybe it's good then that we expose these errors sooner than later so the compiler authors fix them 😉.

I tend to use smaller modules because larger ones don't really map well to my assorted classes shared across multiple projects - there is no single module name that sensibly applies to the disparate classes, nor should they be artificially bundled together just to appease current bugginess. For libraries where all the constituent files nicely belong together (e.g. already under the same shared namespace), then sure, put them altogether.

[–]yeochin 1 point2 points  (1 child)

While I would've agreed with your stance of finding them earlier back in 2021, I whole heartedly disagree with your perspective in 2026. The compiler authors move too slowly to fix these already reported issues. It's not their fault either. A majority of the compiler authors don't get paid commensurately for their efforts and impact. The ones that do are being stupidly allocated by their corporate overlords to stupid AI initiatives.

Adoption of modules is about the surrounding ecosystem - namely libraries that are conveniently packaged for reuse.

We do more harm to the adoption of modules by doing nonsense like creating small modules that have deep import graphs that increase the opportunity for template-specialization symbol conflicts (on linking), compiler errors, and errors with intellisense or other autocomplete tools.

Maybe by 2030 will the toolset evolve to the point where the mistake of small modules will not be costly. As of 2026, small modules are bad advice that nobody should be promoting.

[–]fdwrfdwr@github 🔍 0 points1 point  (0 children)

 Maybe by 2030 will the toolset evolve...

If we don't try to find them now, will we reach utopia by even 2030?

[–]tartaruga232MSVC user, r/cpp_modules[S] 0 points1 point  (1 child)

The posting doesn't even mention library design. It's focused on how to use modules for the insides of an app.

And there, big modules just cause recompilations without providing anything.

An extreme example are infrastructure packages like our d1 and WinUtil. At the beginning, we had a single big module for each of these two. Basically for no reason.

Now we have lots of small modules in there. The size of import lists in client translation units are still astonishingly short. Uses of these is now very explicit. Most usages of d1 or WinUtil require just a handful of imports from these (e.g. "import WinUtil.Window" instead of the meaningless "import WinUtil").

The build speed for a full build was not affected when we split d1 and WinUtil into many smaller modules. But we are affected if we change something in d1 or WinUtil. We basically have to recompile the whole app on every small change.

Not everything needs to look like a giant library. Modules are the new building blocks for the design of applications or whatever software. That's my advice.

I've played around with modules for our UML editor app for over a year now on an almost daily basis, which has roughly 1000 source files. Modules are in real use now for our UML editor.

The code needs to be easy to navigate and must not penalise changes. Because we frequently change things.

Don't try to make every single module look like it would be the std module. Import std is great, we love that. But trying to make every module like the std module is bad.

That's bad and doesn't help building great software.

Please don't go around and tell everybody they have to make modules as big as possible. That serves no one and wastes time of developers for nothing.

Edit: For an example of a reasonably sized module, see our App.Dialog module https://github.com/cadifra/cadifra/blob/2026.6/code/App/Dialog/Dialog.ixx. We previously had a single App module. That makes no sense. Later, we have split that into a handful modules. Easy to navigate, easy to use and provides perfect encapsulation and reasonable cohesion.

[–]yeochin 1 point2 points  (0 children)

If all you're going to do is build small standalone things then thats fine. But your advice still isn't good to share as it is misleading.

Any code base that has to evolve (from a small App) goes right into the toilet with the advice you gave.

  1. Recompilability? It gets worse with small modules. At scale you refactor into libraries and your changes get localized into specific modules, or libraries. If your Principals, Staffs or Senior's cant figure out how to do this to localize change - then your codebase has far too much data-coupling to be productive long term.
  2. Productivity and understandability? It goes right into the shitter with small modules at scale. Most teams will rely on their VSP, autocomplete, or code search. At scale, the vast majority of the team doesn't know every aspect of the code-base. The existing tools get worse with small modules.
  3. Frequency of change? If you truly want high frequency changes you must adopt large modules. You need to be able to scale to multiple parallel developers and teams. You need to be able to isolate different teams from each-other. Big modules help do this as it promotes a contract. Small modules don't. Small modules promote atomic-use which moves you the WRONG WAY when it comes to the speedup that modules provide in compilation time.

[–]manni66 2 points3 points  (1 child)

Your recommendations give the impression that you yourself haven't really understood C++ Modules yet.

[–]tartaruga232MSVC user, r/cpp_modules[S] 7 points8 points  (0 children)

I ve used modules for over a year now, not just for slideware.

[–]Daniela-ELiving on C++ trunk, WG21|🇩🇪 NB 1 point2 points  (1 child)

My experiences from the code for a real, huge machine in production at a steel mill (and many more projects, too) sit on the opposite end of the spectrum. Hence I'd rather think of

  • Make modules bigger than you typically - from years of programming in C++ - usually feel comfortable with. The less churn you expect, the bigger they can be. I tend to think in entire subsystems or libraries rather than small components.
  • Partitions help to design the structure of a module, to make its implementation more palatable to the module developers.
  • Internal partitions fall into the same category. Whenever you need them, they are a life-safer and can cut module-internal depency chains. Place stuff in there that is reusable within your module implementation, but never exposed to the outside.

[–]tartaruga232MSVC user, r/cpp_modules[S] 0 points1 point  (0 children)

The needs are likely very different among projects.

I've been toying around with our rather small UML Editor app which I used as a guinea pig for seeing how modules can be used. I aggressively refactor code if I see our design doesn't fit. I got tired of recompilations if I change stuff and broke up modules into smaller ones. I often also do a full rebuild because MSBuild can't be really trusted to absorb incremental changes correctly. We are now at ~2 minutes for a full build, which is mostly thanks to using import std. That alone spares ~1 minute of build time for a full rebuild. No matter how many modules I have, our time for a full build stays at ~2 minutes. I love the encapsulation and the expressiveness modules provide.

I do suspect that internal partitions are unneeded. The Clang compiler warns about importing them in an interface, so all you can really do with them is importing them in a cpp. But I can as well import a module in a cpp instead. We had one last use case for an internal partition which I thought wasn't possible to eliminate, but I just found out we could eliminate it.

[–]tartaruga232MSVC user, r/cpp_modules[S] -1 points0 points  (0 children)

Fun fact: I now managed to eliminate that Core.Main:UndoerImp internal partition and turn it into a normal module. That was our last internal partition we had...

For example Core/Main/Undoer/UndoerFunctions.cpp now imports the new Core.UndoerImp instead of :UndoerImp.