If you’ve ever stared at a blank folder wondering where do I even start?, you’re not alone. I’ve been there—more than once.
There are plenty of widely used project layouts with their own philosophy. These include, but are not limited to:
Autotools
With it’s classic approach for portable builds using GNU Autotools.Bazel
For structuring and building C++ projects with multiple packages and targets.Meson
A build system with integrated testing, linting, and benchmarking.Pitchfork
A set of conventions for modern C++ projects, with it’s own project layout.
Over time, I discovered that a good project structure is like a well-organized workshop: tools where you expect them, clear labels, and no mystery boxes.
In this post, I’ll walk you through the Canonical Project Structure (inspired by P1204R0), why I love it, and how I use it with modern CMake. Think of this as both a guide and a story from my own experience.
Structure
Here’s the big picture: your project should feel predictable. When someone opens it, they should instantly know where the source lives, where tests hide, and where docs whisper their secrets.
A minimal example looks like this:
libhello/
├── libhello/ # source code (headers + sources together)
├── tests/ # integration tests
Why this layout? Because splitting include/ and src/ often creates unnecessary friction. Instead, keep headers and sources side by side—it’s faster to navigate and future-proof for modules.
Naming tips I swear by:
.hpp/.cppfor C++.h/.cfor C- Use
<libhello/hello.hpp>for includes—never quotes. - For internals, tuck them under
details/orprivate/.
Tests Layout
Unit tests? Keep them close to the code they test, like a loyal sidekick:
libhello/
├── hello.cpp
├── hello.test.cpp
Integration tests? Give them their own playground under tests/ with subfolders per scenario.
Why This Rocks
- Predictable layout = happy tooling and happier humans
- Easier packaging across ecosystems
- Headers + sources together = less hunting, more coding
- Clean testing model: unit tests nearby, integration tests realistic
Using points mentioned above the final result of the C++ project called libhello would looks like this:
libhello/
├── libhello/
│ ├── hello.hpp
│ ├── hello.cpp
│ └── hello.test.cpp
├── tests/
└── basics/
└── driver.cpp
CMake – Structure in Action
Instead of drowning in boilerplate, here’s what makes my setup shine:
- FetchContent for dependencies: No submodules, no manual cloning—CMake handles it all.
- Toolchain flexibility: Automatically fetches and applies toolchain files for consistent builds.
- Strict compiler warnings: Centralized via helper modules, with easy toggles for
WARNINGS_AS_ERRORS. - Preset-driven builds: Reproducible configurations for local dev and CI/CD.
- Integrated testing: GoogleTest pulled in via FetchContent, ready for unit and integration tests.
- Future-proof: Layout aligns with C++20 modules and modern packaging workflows.
Getting Started
Ready to roll? Here’s the quick start guide:
# Clone the template into libhello/
git clone \
https://gitlab.com/sorhanp/canonical-project-structure-template.git \
libhello
# Enter the project directory
cd libhello
# Configure using CMake presets (Debug build)
cmake --preset gcc-debug
# Build the project
cmake --build --preset gcc-debug-build
# Run unit tests
ctest --preset gcc-debug-testThat’s it—no submodules, no headaches. Just modern CMake doing its thing.
Closing Thoughts
This structure isn’t just theory—it’s something I use every day. It keeps my projects clean, portable, and ready for the future. If you’ve been struggling with messy layouts or brittle builds, give this a try.
And remember: a good structure is like a good habit—it pays off every single time you build.
-sorhanp