Erbsland C++ Code Style
This document defines the portable code-style baseline for Erbsland C++ libraries and applications.
Project-specific guidelines may refine or override semantic rules for their domain.
The project’s .clang-format file is authoritative for mechanical formatting.
In this document, must marks a requirement, should marks the expected default unless there is a concrete reason to deviate, and may marks an optional choice.
Formatting
Format every changed C++ file.
Accept .clang-format decisions for indentation, line length, braces, wrapping, spacing, and includes.
Formatting that requires manual attention:
Use empty lines to separate logical blocks.
Do not add empty lines between adjacent documented declarations or inline definitions in a header.
Use one empty line around namespace-scope class, struct, enum, and function definitions.
Run the project’s validation command after making changes, if one exists.
Naming
Types and public type aliases use
PascalCase; methods, free functions, local variables, and parameters usecamelCase; member variables use_camelCase.Namespace-scope and static constants use
cCamelCase. Enum-like static constants may usePascalCase, as inColor::Red.A single straightforward template parameter may use
T; multiple or descriptive parameters usetCamelCase.Namespaces use lowercase nested names, such as
erbsland::unittest.Preprocessor macros use
UPPER_CASEand anERBSLAND_<LIBRARY>_prefix in Erbsland Core libraries and applications. Do not use macros for constants.Treat initialisms as words in identifiers, such as
HttpServerandparseUtf8. Keep the documented spelling of domain-specific abbreviations.
Files and Includes
Begin each C++ file with the project’s two-line copyright block. Put
#pragma oncedirectly after it in headers.Use
.hppfor headers,.cppfor implementations, and.tppfor extracted templates.Each
hpp/cppmodule should have one primary class, struct, enum, alias, or logical method collection. Closely related implementation helpers may share a module.Match the primary type and filename, and mirror namespaces in the source directory structure. Directories may subdivide a large namespace without adding another namespace.
Put private implementation details in an
impldirectory and matchingimplnamespace.Directly include every declaration a file uses; do not rely on unrelated transitive includes. A
cppfile includes its corresponding header first.Handwritten C++ source files must not exceed 500 physical lines. Split larger implementations by logical responsibility. Name parts
Class_part.cpp,Class_part.hpp, orClass_part.tpp. Includetppparts at the bottom of the owning header, without an include back to that header.Do not edit generated files directly. Modify their source or generator and regenerate them.
CMake
Use one
CMakeLists.txtin each directory that contains source files.Add only local files with one flat
target_sources(<target> PRIVATE ...)block. Add nested directories withadd_subdirectory(...)beforetarget_sources.Sort files and subdirectories alphabetically.
Class Organization
Group a class with repeated access specifiers: one empty line before each section, none between its declarations, and an
optional lowercase // label.
Simple value structs and dependency constraints may require a smaller or different layout.
The usual section order is:
Private friends, nested types, enums, and aliases in dependency order.
Public types in dependency order.
Default and other constructors, destructor, copy and move constructors, then copy and move assignment. Put explicitly defaulted or deleted members in a final defaults group.
Main public operations.
Overrides, using one
public: // implements Basesection per base.Operators: comparison, arithmetic, logical, then other operators.
Accessors: condition tests first, then each attribute’s accessors together.
Other public tools, grouped only when this improves navigation.
Conversions:
to...methods followed by staticfrom...and other factories.Private and protected methods.
Data members, grouped by access.
Modern C++
Use portable C++20 features. Prefer concepts, structured bindings, designated initialization, and range-based loops when clearer.
Use trailing return types for non-void functions where the syntax permits, such as
auto create() -> std::string. Usevoid function()for ordinary void functions and explicit-> voidon non-generic lambdas. Use concrete return and parameter types for non-generic functions and lambdas.Use
autofor values when the type is apparent or clearer; useconstfor immutable values andconstexprwhen usable at compile time.Add
[[nodiscard]]when silently discarding a result is likely to be a mistake. Addnoexceptonly when the operation is guaranteed not to propagate an exception.Do not use
static_cast<void>(...)merely to silence[[nodiscard]]. Select an operation whose contract matches the intended use, handle the result, or remove[[nodiscard]]when discarding the result is genuinely a normal and safe use of that API.Mark intentionally unused named parameters
[[maybe_unused]]; omit an unused private overload-disambiguation tag’s name.Mark overriding functions
overrideand classes deliberately closed to extensionfinal.Express ownership explicitly. Use values or references by default,
std::unique_ptrfor unique ownership,std::shared_ptronly for shared ownership, raw pointers for deliberate non-owning or native boundaries, andstd::optionalfor an absent value.Expose raw pointers through public APIs only at unavoidable interoperability boundaries. Use an explicitly named
Unsafe...alias or wrapper and document ownership, lifetime, nullability, and mutability.Use lazy initialization for immutable or expensive data whose construction should be deferred.
Erbsland Core Integration
Prefer
Stringfor read-only strings,""_elfor literals, andStringFormatfor formatting.Prefer
StringEditoras a local mutable working value for explicit in-place editing or small construction tasks. Do not use it as the default parameter or read-only storage type.For joining fixed string segments, use
String::fromJoined(); for a dynamic collection, prepare and join aStringList. UseAnyStringBuilderfor width-independent construction and direct formatted-value appends.Prefer regular UTF-8 types; use U8, U16, or U32 types when an API boundary or algorithm requires them.
Prefer Erbsland Core types and algorithms before
std::types and algorithms.In portable unit tests, do not construct UTF-8 text with
\x??escapes because their interpretation differs between compilers. When using Erbsland Unit Test, useth::stdStringFromHex()when raw bytes are required.
Comments and API Documentation
Comment Format
Use
///and Doxygen@commands for API documentation, without empty comment lines.Use
//for short implementation notes. Use/* ... */only when an inline annotation or generated layout makes it clearer than a line comment.@seedoc{/path}links to a documentation page, and@seeref{id}links to a reference target.@wipmarks work in progress; ask the project owner before modifying the marked API.Required Documentation
In public and internal APIs, document every class, struct, enum, public type alias, public constant, namespace-scope function, and public method.
Start with one brief line and document every parameter, non-void return value, thrown exception, and relevant edge or error case.
Move extensive explanations to linked reference or topic documentation.
Give every data member and enum member a brief trailing
///<description.Explicitly defaulted or deleted special members don’t need documentation, they must be grouped under
// defaultsor// defaults/deletionsor a similar block.A trivial getter or setter needs only a one-line description without
@paramor@return.An override inherits the API documentation of its base declaration and must not duplicate that documentation. Group overrides under
public: // implements Baseorprotected: // implements Baseand document only relevant behavioral differences at the derived-class level.Cryptographic Implementations
Cryptographic code must be written for transparent security review as well as functional correctness.
Keep the implementation in the same logical order as the defining specification wherever practical. Prefer direct, readable transformations over compact or clever formulations.
API documentation for an algorithm implementation must name the governing specification and the relevant section.
Add inline comments before each substantive algorithm step that identify the corresponding specification section and relate the specification’s notation or formula to the variables and operations in the code.
Document security-relevant bounds, representation choices, precomputations, and deviations from optional parts of the specification where they are enforced.
Keep secret-state lifetime and erasure behavior visible at the point where secret intermediates are created, transferred, or released.
Test Status
End every documented class, struct, and namespace-scope function API block with exactly one test-status marker. Do not mark constructors, methods, operators, or other members. Only use these marker in our primary code base in the
srcdirectory.@tested{ExampleTest OtherTest}lists one or more test-suite class names separated by spaces. Names must end inTest; paths and method selectors are invalid.@notest{reason}explains in one line why a test is not applicable.@needtest{reason}identifies missing coverage in one line.