Compiler Is All You Need
A compiler-first journey into C++20 modules, self-hosting tools, and lesser programming.
By Pawel Wodnicki · September 2, 2026
The Compiler Was All I Had
A compiler was all I had when I started my professional journey as a C++ programmer more than 30 years ago. It was a different era of software development tools. They worked on machines with a fraction of modern computing power, and perhaps I am just nostalgic, but those tools were a joy to use.
More than 30 years later, everything about C++ and software development has changed, yet somehow it has all stayed the same. We use computers with vastly more memory and astonishing clock speeds, but somehow the development experience can feel worse than before.
And Then Modular C++20 Arrived
C++20 changed everything for me when it finally introduced modules. After all those years of waiting, modular programming was finally part of the language itself. It was not a new metaprogramming paradigm, a template trick, a library, or a compilation hack. It was simply a language feature.
After years of crafting software and firmware in a more-or-less modular way, I was ready to embrace C++ modules. Just give me the code I can use. By 2026, I was tired of waiting—but the tools had reached the point where we could do something about it.
Introducing modules.cpp
modules.cpp is my attempt at a modern, modular C++ project that breaks with the past. No more legacy code—we will wrap it when we must—and no more layers of meta-meta-something when a direct language feature will do.
The project follows a few deliberately strict ideas:
- Import the module; do not include the header.
- Treat
#as a banned character wherever possible. - Prefer
constexprvariables and functions to macros. - Define APIs through module interfaces and classes.
- Export functions, structures, enumerations, and concrete classes from core modules.
- Build the tools the project needs, except for the compiler and basic platform services.
- Reuse code until what remains is modular and minimal.
The aim is not novelty for its own sake. The aim is a codebase whose boundaries are explicit and whose machinery can be understood from the source tree.
Self-Everything Is the Name of the Game
You first bootstrap the project on a POSIX-compatible machine. The compiler is all you need, with a decent shell and C locale implied:
./bootstrap.sh
./build.sh
./test.sh
./document.sh
Once bootstrapped, the project can build itself. An mm.mdy manifest in almost
every participating directory describes the project tree, targets, sources,
and module dependencies. MDY combines simple key-value metadata with a
Markdown-like body, and the project includes an mdy application to read and
render it.
When the build is finished, test it. modules.cpp includes unit, integration, and regression tests for test-driven development.
When the tests pass, document it. Running document or document.sh fills the
out/ directory with static HTML beginning at out/index.html. The generated
documentation retains a folder structure that closely resembles the project
itself: self-similarity by design.
The end of testing is not the end of development. It begins the next turn of the spiral. In each cycle, the project models new features and verifies the updated model before feature code is written. The model is expressed in C++, keeping the distance between modeling and implementation small.
During active development, the project continually checks itself against its own rules.
What Are We Producing Here?
The cardinal rule is simple:
The current version must be able to build the next version.
All compiled and generated output lives under out/, preserving the source
tree where useful and adding locations such as out/bin/ for installed
commands.
The top-level structure reflects the development workflow:
apps/ applications, including mdy
docs/ project documentation
models/ the abstract project model
modules/ reusable mm modules
tests/ unit, integration, and regression tests
tools/ project-owned development tools
The tools are intentionally direct:
| Tool | Role |
|---|---|
build |
Builds the project from source |
check |
Checks source against project rules |
model |
Models and verifies the project structure |
shell |
Provides the project-owned shell workflow |
test |
Builds and runs tests |
mdy |
Reads manifests and renders documentation |
Sometimes we need to start over, so there is also a clean wrapper and a
clean.sh script.
The wrapper is intentionally self-referential: it is a shell script that calls the project's shell tool, which then runs the corresponding shell script. The project builds the shell tool that runs the wrapper around its own workflow.
More Is Less, More or Less
To me, the current state of software development often means that more of everything leads to less quality, utility, understanding, value, and ultimately freedom of choice.
To turn that around, we have to do less by choosing a minimal, modular approach to software development.
That leads directly to a lesser programming manifesto.
Lesser Programming Manifesto
To do more, we choose to do less.
We model with less abstraction, leading to less code, documentation, testing, and maintenance.
We can do less because the codebase is consistent with its own rules—rules that are self-modeled, self-checked, and self-enforced by tools included in the project.
The same principle applies to C++ language features. C++ has evolved for more than 40 years and now supports a rich, sometimes confusing set of features and paradigms: from better C, through object-oriented and metaprogramming styles, to modular programming.
The choices in modules.cpp are deliberate: use modern features and reject the features that lead to poor quality or unsafe code.
Seed Release 1.0
Software is never done. This is seed release 1.0, intended to grow both up and down the version tree.
Explore the modules.cpp project page or view the source on GitHub.
Pawel Wodnicki, 2026