STYLE
Defines C++ project conventions for package layout, naming, error handling, memory management, and testing, based on Lakos and Envoy.
What this file does
Defines C++ project conventions for package layout, naming, error handling, memory management, and testing, based on Lakos and Envoy.
When to use it
- Starting a new C++ library with strict dependency management
- Adopting Lakos-style package groups and namespace conventions
- Standardizing error handling and noexcept usage across a team
- Using async CUDA with futures and promises
Assumes this stack
Organization
The project's structure largely follows the conventions from John Lakos' book "Large Scale C++ Software Design" ([1], [2]).
All library code belongs to a component; all components belong to a package; all packages belong to a package group; and there are no dependency cycles between components, packages, and package groups (§2.1 of [1]). For example,
sxt/base/iterator/counting_iterator.{h,cc,t.cc}
are the header, source, and unit tests for the component "counting_iterator". "counting_iterator" belongs to the package "iterator", and "iterator" belongs to the package group "base".
If another component sxt/multiexp/pippenger_multiprod/multiproduct depends on
base/iterator/counting_iterator, then no component in the package group "base"
can depend on a component in the package group "multiexp" as that would introduce
a cycle (§3.5 of [1], [3]).
All packages are given a unique namespace. For example, all components within
the package base/iterator use the namespace "basit". The first three letters
of a package namespace uniquely identify the package group (e.g. "bas"
identifies the package group "base"); following letters uniquely identify the
package within the package group (§2.10 of [1]). Package namespace names don't have to
be descriptive, but should be short and satisfy the uniqueness requirements.
Naming
We follow the C++ standard library naming convention. Class names, variables, functions, and concepts use snake case.
Class member names are suffixed with an underscore:
class abc {
private:
int x_;
};
Template parameters use camel case with the first letter capitalized:
template <class MyType>
void f(MyType t) {
// ...
}
Error handling
Our error handling guidelines are inspired by Envoy's style guide ([4]).
Error codes and exceptions should be used to handle normal control flow. Crashing is a valid error handling strategy and should be used for errors not considered part of normal control flow ([5]).
To make errors more explicit, we use noexcept for functions that either don't throw an exception or would only throw exceptions outside of normal control flow (Item 14 of [6]). For example,
std::vector<int> copy_and_sort(const std::vector<int>& xv) noexcept {
std::vector<int> res{xv.begin(), xv.end()};
std::sort(res.begin(), res.end());
return res;
}
copy_and_sort might throw std::bad_alloc and noexcept will cause the function to terminate; but such an error would be outside of normal control flow, so termination is acceptable.
Memory Management
We make extensive use of RAII and allocator-aware containers to manage device memory and achieve certain host-side optimizations.
See [9], [10], and [11].
Futures and Promises
In order to get the most out of GPUs and eventually scale to using multiple GPUs, we use the asynchronous versions of CUDA API functions.
To make writing async code easier, we adopt the future-promise design from seastar.io ([12]) and use c++20 coroutines ([13]).
Testing
We try to follow the guidelines from Kevlin Henney's talk "Structure and Interpretation of Test Cases" ([7], [8]).
In addition to checking correctness, tests also serve as documentation and should be readable and describe code's behavior.
References
1: John Lakos. 2019. Large-scale C++ software design, Volume 1.
2: John Lakos. Overview of [1].
3: John Lakos. Advanced Levelization Techniques.
4: https://github.com/envoyproxy/envoy/blob/main/STYLE.md#error-handling.
5: Matt Klein. Crash early and crash often for more reliable software.
6: Scott Meyers. Effective Modern C++.
7: Kevlin Henney. Structure and Interpretation of Test Cases.
8: Kevlin Henney. Programming with GUTs.
9: Pablo Halpern, John Lakos. Value Proposition: Allocator-Aware (AA) Software.
10: John Lakos. Local ('Arena') Memory Allocators.
11: Pablo Halpern. Allocators: The Good Parts.
12: Avi Kivity. Building efficient I/O intensive applications with Seastar.
13: Gor Nishanov. C++ Coroutines: Under the covers.
What's inside
7 sections covering organization, naming, error handling, memory management, futures/promises, testing, and 13 references
Change this for your project
- Replace
sxt/with your own package group prefix - Replace
basitwith your own package namespace abbreviation - Replace
base/iterator/counting_iteratorwith your own component path
Where it goes
Save in docs/ or the repository root. Gives agents and new contributors a map of the codebase.
Worth borrowing
- Use noexcept to signal functions that only throw on catastrophic errors
- Suffix class member names with underscore to distinguish from local variables
- Adopt Lakos levelization to prevent dependency cycles between package groups
Related Documents
Design Document: BharatSeva AI
Describes a 10-agent AWS system that helps India's informal workers access government schemes via voice-first, serverless architecture.
OpenClaw Enterprise Transformation Plan
Transforms a single-user AI agent into a dual-mode platform supporting both viral open-source and Fortune 500 enterprise deployments through phased security, IAM, audit, multi-tenancy, and Kubernetes features.
Qwen Image and Edit: Open-sourcing and Local GGUF Generations with Lightning
Documents the Qwen-Image and Qwen-Image-Edit models, covering architecture, training, benchmarks, ComfyUI setup, and prompting techniques for local GGUF deployment.
University of Guelph Rocketry Club - Complete Tech Stack
Documents the full tech stack of a university rocketry club website with AI chatbot, member management, and project showcases.