Version: 1.8.0 Updated: 2025-12-20 C++ Standard: C++20/23
A concise style guide for modern C++ projects.
| Version | Date | Changes |
|---|---|---|
| 1.8.0 | 2025-12-20 | Null pointer initialization: use static_cast for AAA |
| 1.7.0 | 2025-12-18 | Attributes on their own line |
| 1.6.0 | 2025-12-18 | Multiline lambdas: same formatting as functions |
| 1.5.0 | 2025-12-16 | Multiline declarations: return type follows closing paren |
| 1.4.0 | 2025-12-14 | Remove explicit type wrapper requirement for function calls |
| 1.3.0 | 2025-12-10 | Always use braces for if/for/while statements |
| 1.2.0 | 2025-12-10 | Call site arguments follow same formatting as declarations |
| 1.1.0 | 2025-12-10 | Long parameter lists: one parameter per line |
| 1.0.0 | 2024-12-10 | Initial release |
- Clarity over brevity - Readable code, clear intent
- Const by default - Right-side const, immutable when possible
- Early returns - Reduce nesting, handle errors immediately
- Self-documenting code - Clear names over comments
// Literals - type clear from value
auto x = 42;
auto pi = 3.14;
auto name = "hello";
// Function calls - auto deduces return type
auto result = compute();
auto count = vec.size();
// Factory functions
auto ptr = std::make_unique<Foo>();
auto opt = std::make_optional(42);
// Collections with initializers
auto vec = std::vector<int>{1, 2, 3};
auto map = std::unordered_map<std::string, int>{};
// References and pointers
auto const& ref = container;
auto* ptr = get_pointer();
// Null pointer initialization - use static_cast for type
auto const* ptr = static_cast<Foo const*>(nullptr);// Short declarations - return type on same line
auto foo() -> ReturnType;
auto empty() const -> bool;
// Multiline declarations/definitions - closing paren on own line, return type follows
auto create_connection(
std::string_view host,
std::uint16_t port,
ConnectionOptions const& options
) -> Result<Connection>;auto const& ref = value; // Not: const auto& ref
int const* ptr; // Not: const int* ptr
std::string const& name() const; // Not: const std::string&| Element | Style | Example |
|---|---|---|
| Types (class, struct, enum) | PascalCase |
BuildGraph, NodeType |
| Functions, methods | snake_case |
parse_file(), add_node() |
| Variables | snake_case |
node_count, file_path |
| Private members | snake_case_ |
nodes_, next_id_ |
| Anonymous namespace constants | SCREAMING_CASE |
MAGIC_VALUE |
| Compile-time constants | snake_case |
constexpr auto max_size = 64; |
| Enum values | PascalCase |
NodeType::Command |
| Namespaces | snake_case |
namespace my_project |
// SPDX-License-Identifier: MIT
// Copyright (c) 2024 authors
#pragma once // Header guard
#include "project/module/foo.hpp" // 1. Corresponding header (.cpp)
// 2. Project headers
#include <memory> // 3. Standard library
#include <string>
namespace project::module {
// Anonymous namespace for internal linkage (not static)
namespace {
auto helper() -> void { }
auto const INTERNAL_CONST = 42;
}
// Public interface...
} // namespace project::module// Use [[nodiscard]] for values that shouldn't be ignored
[[nodiscard]]
auto compute() -> Result<int>;
// Parameter passing:
// - Small types by value: int, bool, enum, NodeId
// - Read-only strings: std::string_view
// - Read-only large objects: const&
// - Sink parameters: by value, move into place
auto process(std::string_view text) -> Result<void>;
auto add(Node node) -> Result<NodeId>; // takes ownership
// Long parameter lists - one parameter per line
auto create_connection(
std::string_view host,
std::uint16_t port,
ConnectionOptions const& options,
std::chrono::milliseconds timeout
) -> Result<Connection>;
// Fits on one line - keep together
auto add(int a, int b) -> int;
// Call sites follow the same pattern
auto conn = create_connection(
"localhost",
8080,
default_options,
std::chrono::milliseconds{5000}
);// Always use braces
if (condition) {
return early;
}
for (auto const& item : items) {
process(item);
}
// Early returns over nesting
auto get_node(NodeId id) -> Node*
{
if (id == 0) {
return nullptr;
}
if (id >= nodes_.size()) {
return nullptr;
}
return &nodes_[id];
}// Result<T> for fallible operations
auto parse() -> Result<Config>;
// Propagate errors with early returns
auto process(Path const& path) -> Result<void>
{
auto content = read_file(path);
if (!content) {
return unexpected<Error>(content.error());
}
auto parsed = parse(*content);
if (!parsed) {
return unexpected<Error>(parsed.error());
}
return {}; // Success for Result<void>
}// Designated initializers
auto node = Node{
.type = NodeType::File,
.path = "foo.c",
};
// Structured bindings
for (auto const& [key, value] : map) {
process(key, value);
}
// std::optional for optional values
auto find(Key k) -> std::optional<Value>;
// std::variant for type-safe unions
using Token = std::variant<Literal, Identifier, Operator>;
// std::span for non-owning views
auto process(std::span<std::byte const> data) -> void;
// Lambdas - short form
auto pred = [&ctx](auto const& item) { return item.matches(ctx); };
// Lambdas - multiline (same formatting as functions)
auto create_file = [&](
std::string_view path,
std::string_view name
) -> NodeId {
// ...
};
// Concepts (C++20)
template<typename T>
requires std::integral<T>
auto count_bits(T value) -> int;
// ranges (C++20/23)
auto names = items | std::views::filter(is_valid)
| std::views::transform(&Item::name);Only when explaining why, not what:
// Good - explains non-obvious reason
// Recursively expand to handle nested variable refs like \"$(VAR)\"
return expand(std::string_view{result});
// Bad - states the obvious
// Loop through all nodes
for (auto const& node : nodes_) { }struct: Public data, minimal logic, POD-like
struct Point { int x; int y; };
struct Config { std::string path; int timeout = 30; };class: Encapsulation, invariants, private state
class Graph {
public:
auto add_node(Node n) -> NodeId;
private:
std::vector<Node> nodes_;
};- Use
autofor variable declarations - Trailing return types
- Right-side
const -
[[nodiscard]]on important returns - Anonymous namespace for internal linkage
- Early returns for error cases
- Long parameter/argument lists formatted one-per-line
- Always use braces for control flow statements
- Comments explain "why" not "what"