ModernCppStarter: a CMake template for C++ projects that keeps dependencies reproducible
🚀 Kick-start your C++! A template for modern C++ projects using CMake, CI, code coverage, clang-format, reproducible dependency management and much more.
At a glance
- What is it?
- ModernCppStarter is a GitHub template that wires together CMake, CPM.cmake, clang-format, Codecov and GitHub Actions into one working C++ repository. It is a scaffold, not a library, and the article covers what it installs, how the subprojects fit together, and where the template stops helping.
- Who is it for?
- Adopt ModernCppStarter if you are starting a C++ library or executable and want CMake, tests, formatting and CI already wired together, and if you are willing to rename Greeter everywhere before writing real code. Do not adopt it if you need a package manager with a lockfile and binary caching, or if you already have a build system you are happy with.
- Can I use it commercially?
- Yes. Unlicense is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 122 days ago.
- What is it written in?
- Mainly CMake, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on September 29, 2026, and from our analysis. They are not legal advice.
DEEP OPEN-SOURCE ANALYSIS
The boilerplate problem ModernCppStarter targets
Starting a C++ project from an empty directory means writing a CMakeLists.txt, deciding how tests link against the library, setting up a CI matrix for three operating systems, and picking a way to pull in dependencies. The README states the template is "the result of learnings from many previous projects" and should reduce the work required to set up a modern C++ project. That is the whole scope: it is a starting point for a repository, not a runtime library you link against.
The intended audience is someone who already knows C++ and wants the surrounding infrastructure decided for them. The repository is organised around that assumption. The outer CMakeLists.txt defines only the library, while tests and other subprojects live in their own directories, so the library can be consumed without dragging the test harness along. If you are looking for a build system to drop into an existing codebase, this is the wrong shape of tool: it expects to own the top-level layout.
How the repository is laid out and how the pieces connect
The top level contains .clang-format, .cmake-format, .github/, CMakeLists.txt, all/, cmake/, codecov.yaml, documentation/, include/, source/, standalone/ and test/. The split matters. include/ and source/ hold the library, standalone/ builds an executable that links it, test/ builds the test suite, documentation/ drives Doxygen, and all/ is a superbuild that exposes every subproject at once.
Dependency management runs through CPM.cmake, which the README describes as providing reproducible dependency management. Formatting is enforced by clang-format and cmake-format through Format.cmake. Versioning and header generation come from PackageProject.cmake, which the README says produces an installable target with automatic versioning information. Continuous integration is GitHub Actions, coverage is Codecov, and documentation is published to GitHub Pages on release. Each of those is a separate project by the same author, pulled in rather than reimplemented.
The all/ directory is the part worth understanding before you commit. It exists so that during development all targets are built together, which the README says exposes all subprojects to your IDE and avoids redundant builds of the library. That is a development convenience, not the shipping configuration.
Installing ModernCppStarter and building the standalone target
There is no package to install. The README says to use the repository as a GitHub template, then replace every occurrence of Greeter in the relevant CMakeLists.txt with your project name. Capitalisation is load-bearing: Greeter is the project name, greeter appears in file names. You also rename the include/greeter directory and update the matching #include lines.
Once renamed, the standalone executable builds with three commands from the README. Run them from the project root.
cmake -S standalone -B build/standalone
cmake --build build/standalone
./build/standalone/Greeter --helpThe first command configures the standalone subproject into build/standalone, the second compiles it, and the third runs the resulting binary with --help. If the rename was done correctly, the help output carries your project name rather than Greeter. A configuration error here usually means a stale Greeter reference in a CMakeLists.txt.
The all/ directory builds every target at once with a different configure step.
cmake -S all -B build
cmake --build buildAfter that build completes, the README lists the same targets as the split builds: ./build/test/GreeterTests for tests, cmake --build build --target fix-format for formatting, ./build/standalone/Greeter --help for the executable, and cmake --build build --target GenerateDocs for documentation.
Running the test suite and checking code style
The test subproject is self-contained, so it configures separately from the standalone build. The README gives these commands from the project root.
cmake -S test -B build/test
cmake --build build/test
CTEST_OUTPUT_ON_FAILURE=1 cmake --build build/test --target test
# or simply call the executable:
./build/test/GreeterTestsSetting CTEST_OUTPUT_ON_FAILURE=1 makes failing test output visible instead of only reporting a failure count. Coverage collection is opt-in through a configure flag rather than a default.
cmake -S test -B build/test -DENABLE_TEST_COVERAGE=1The README states that running CMake with -DENABLE_TEST_COVERAGE=1 collects code coverage information. Codecov uploads are wired through the CODECOV_TOKEN secret, which the README instructs you to add to your repository's GitHub secrets.
Style checking needs clang-format, cmake-format and pyyaml on the system, and the README pins them through pip.
pip install clang-format==14.0.6 cmake_format==0.6.11 pyyaml
cmake --build build/test --target format
cmake --build build/test --target fix-formatThe format target reports style changes and fix-format applies them. Pinning clang-format to a specific version is deliberate: different clang-format releases reformat differently, so an unpinned local install will disagree with CI.
Sanitizers, static analyzers and the tools.cmake switchboard
The test and standalone subprojects include cmake/tools.cmake, which imports optional tools through CMake configuration arguments. Sanitizers are selected with a single variable, and the README lists Address, Memory, MemoryWithOrigins, Undefined, Thread and Leak as accepted values, with combinations written in quotes and separated by semicolons, for example 'Address;Undefined'.
Static analysis is a second switch. Setting -DUSE_STATIC_ANALYZER to clang-tidy, iwyu, cppcheck, or a semicolon-separated combination enables them, and the README notes that analyzers find configuration files such as .clang-format automatically. Extra flags go through CLANG_TIDY_ARGS, IWYU_ARGS or CPPCHECK_ARGS.
This is on-demand rather than always-on, and that is the right call. Thread and Memory sanitizers cannot be combined with each other, and MemoryWithOrigins is a distinct mode rather than an alias. Enabling everything by default would make the normal build slow and would fail on toolchains that lack a given sanitizer, so the template leaves the choice to the configure step. The cost is that you have to remember to run those configurations; nothing in the default build path forces them.
Where the template stops helping
ModernCppStarter does not build or fetch your dependencies for you in a binary sense. CPM.cmake is a CMake module that pulls sources at configure time, which the README frames as reproducible dependency management. That is a different guarantee from a package manager that ships prebuilt binaries and a lockfile. For a project with a large dependency graph, configure-time source builds are slow, and there is no binary cache in the template as described.
The template also assumes GitHub. Continuous integration is GitHub Actions, coverage is Codecov, documentation publishes to GitHub Pages, and the codecov token goes into GitHub secrets. If your project lives on GitLab or a self-hosted forge, the .github/ workflows are dead weight and you will rewrite them.
Finally, the rename step is manual and unforgiving. Nothing in the README describes a script that performs it, and the capitalisation rule means a find-and-replace on the wrong case produces a build that configures but links against the wrong target name. The README also notes you can remove unused files such as the standalone directory or irrelevant workflows, and that the License can be replaced, so cleanup is expected rather than optional.
ModernCppStarter compared with Conan and plain CMake
Conan is the natural comparison point, and the search data around this project pairs the two directly. The difference is where dependency resolution happens. Conan is a package manager with a client, a central remote, binary packages and a lockfile, so a dependency is resolved once and downloaded as a prebuilt artifact where one exists. CPM.cmake, as used here, is a CMake module that integrates fetching into the configure step and keeps everything inside the CMake build. That means fewer moving parts and no separate tool to install, at the cost of rebuilding dependencies from source and no binary cache.
Against plain CMake with no template, the difference is everything the template has already decided: the library/executable split, the test wiring, the format targets, the sanitizer and analyzer switches, the release-triggered documentation build. You can assemble all of that yourself, and the README links to the upstream projects for exactly that reason. The template's value is that the assembly is already done and the subprojects are known to configure together.
Licence and the cost of keeping up
The repository is released under the Unlicense, which the README also notes can be replaced with a licence suited to your project. Unlicense is a public-domain-style dedication, so it imposes no attribution requirement on the template code you copy. That says nothing about the licences of the dependencies you add through CPM.cmake; those are separate and you should check them yourself rather than assume the template's licence covers them.
Upgrade cost is mostly the cost of the four upstream projects the template depends on: CPM.cmake, Format.cmake, PackageProject.cmake and the GitHub Actions workflows. The last push to this repository was on 2026-05-30, and the most recent release is v0.19.2 from the same date, so the template itself is not dormant. But adopting it does not mean you track it: once you rename Greeter and start writing code, you have forked the scaffold, and pulling later template changes means diffing against your own modifications. Budget for that before you start, not after.
Editorial conclusion
Adopt ModernCppStarter if you are starting a C++ library or executable and want CMake, tests, formatting and CI already wired together, and if you are willing to rename Greeter everywhere before writing real code. Do not adopt it if you need a package manager with a lockfile and binary caching, or if you already have a build system you are happy with. Verify first that your toolchain can build the all/ directory and that your platform's sanitizer set matches what cmake/tools.cmake exposes.
Frequently asked questions
What is ModernCppStarter?
It is a GitHub template repository for C++ projects, built around modern CMake practices with an integrated test suite, GitHub Actions CI, Codecov coverage, clang-format enforcement and dependency management via CPM.cmake. The README describes it as the result of learnings from many previous projects, intended to reduce setup work.
How do I start a new project from ModernCppStarter?
Use the repository as a GitHub template, then replace every occurrence of Greeter in the relevant CMakeLists.txt with your project name, rename the include/greeter directory, and update the matching #include lines. Capitalisation matters: Greeter is the project name while greeter appears in file names.
Does ModernCppStarter use Conan for dependencies?
No. Dependency management runs through CPM.cmake, which the README describes as providing reproducible dependency management inside the CMake configure step, rather than through a separate package manager client.
How do I run the tests in ModernCppStarter?
Configure and build the test subproject, then run the test target or the test executable directly. The README gives CTEST_OUTPUT_ON_FAILURE=1 cmake --build build/test --target test as one option, and ./build/test/GreeterTests as another.
Can I enable sanitizers in ModernCppStarter?
Yes. The README states that configuring CMake with -DUSE_SANITIZER accepts Address, Memory, MemoryWithOrigins, Undefined, Thread, Leak, or a quoted semicolon-separated combination such as 'Address;Undefined'.
What licence does ModernCppStarter use?
The repository is released under the Unlicense. The README notes that you can replace the licence with one suited to your project, which is worth doing if the public-domain dedication does not fit your organisation.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/thelartians-moderncppstarter)
Community notes