This document defines the coding style for this C++ project. The goal is readability, consistency, and safety.
- Background
- C++ Version
- Header Files
- Scoping
- Classes
- Functions
- Other C++ Features
- Ownership and Smart Pointers
- Rvalue References
- Friends
- Exceptions
- noexcept
- Run-Time Type Information (RTTI)
- Casting
- Streams
- Preincrement and Predecrement
- Use of const
- Use of constexpr, constinit, and consteval
- Integer Types
- Floating-Point Types
- Architecture Portability
- Preprocessor Macros
- 0 and nullptr/NULL
- sizeof
- Type Deduction (including auto)
- Class Template Argument Deduction
- Designated Initializers
- Lambda Expressions
- Template Metaprogramming
- Concepts and Constraints
- C++20 modules
- Coroutines
- Disallowed standard library features
- Third-party Libraries
- Nonstandard Extensions
- Aliases
- Switch Statements
- Naming
- Comments
- Formatting
- Line Length
- Non-ASCII Characters
- Indentation
- Function Declarations and Definitions
- Lambda Expressions
- String Literals
- Floating-point Literals
- Function Calls
- Braced Initializer List Format
- Looping and branching statements
- Parentheses
- Switch statements format
- Pointer and Reference Expressions and Types
- Boolean Expressions
- Return Values
- Variable and Array Initialization
- Preprocessor Directives
- Class Format
- Constructor Initializer Lists
- Namespace Formatting
- Horizontal Whitespace
- Vertical Whitespace
- Exceptions to the Rules
- Tooling
- References
- Style covers more than formatting: naming, scoping, ownership, and API design.
- This guide assumes familiarity with C++; it is not a C++ tutorial.
- Optimize for the reader, not the writer.
- Be consistent within this codebase and with
.clang-format. - Avoid surprising or hard-to-maintain constructs.
- Target C++20.
- Avoid non-standard language extensions unless approved; see Nonstandard Extensions.
- Every
.cppfile should have a matching.hfile when it defines reusable APIs (tests and tinymain()files are exceptions). - Use
*.hand*.cppextensions. - Use
snake_casefor file names. - Every file must include the project license header.
- Use
#include <...>for C/C++ standard library headers;#include "..."for project headers. - Headers must compile on their own — include only what they need; put other includes in
.cppfiles. - Expose only the module's public variables, types, and functions; keep implementation details in
.cppfiles. - Use the same license header as the rest of the project.
- Do not use
.inl.hsplit headers; put template/inline definitions in the header when required.
// License comes here
#ifndef PATH_TO_FILE_H
#define PATH_TO_FILE_H
// Include headers
// File content here
#endif // PATH_TO_FILE_H// License comes here
#ifndef MY_PROJECT_NET_H
#define MY_PROJECT_NET_H
#include <cstdint>
#include <string>
namespace my_project::net {
class Socket {
public:
explicit Socket(int fd);
int send(const std::string &data);
private:
int fd;
};
} // namespace my_project::net
#endif // MY_PROJECT_NET_H- All header files use
#ifndef/#define/#endifguards to prevent multiple inclusion. - Guard name is the file path in uppercase:
PATH_TO_FILE_H.
#ifndef FOO_BAR_BAZ_H
#define FOO_BAR_BAZ_H
...
#endif // FOO_BAR_BAZ_H- Include every header whose symbols you directly use.
- Do not rely on a symbol being available through another header's includes.
// OK
#include <algorithm>
#include <vector>
std::vector<int> v;
// Wrong
#include <algorithm>
std::vector<int> v;- Prefer
#includeover forward declarations when practical. - Do not forward-declare symbols you do not own.
- Avoid forward declarations that hide dependencies or break when APIs evolve.
// Prefer
#include "other_module.h"
// Only when include cost is high and dependency is stable
class ExpensiveType;
void register_type(const ExpensiveType &value);- Define functions in headers only when
inline,constexpr, or templates require it. - Avoid non-trivial function bodies in headers; keep public inline bodies short (roughly ≤10 lines) and put larger bodies in
.cppfiles.
// OK in header
inline int square(int x)
{
return x * x;
}
template<typename T>
T clamp(T val, T lo, T hi)
{
return std::max(lo, std::min(val, hi));
}Project order:
- Own header (in
.cppfiles) - Application/project headers
- Third-party headers
- C++ standard library headers
- C/system headers
Separate groups with a blank line; sort alphabetically within each group.
// foo.cpp
#include "foo.h" // 1. own header
#include "bar.h" // 2. application headers
#include <boost/system/error_code.hpp> // 3. third-party
#include <iostream> // 4. standard library
#include <sys/ioctl.h> // 5. system / OS headers- Place project code in named namespaces.
- Use unique names based on the project name and possibly its path.
- Do not use
using namespacein headers. - Do not use inline namespaces.
- Close namespaces with a comment.
- Wrap the entire source file after includes, forward declarations of classes from other namespaces and defines.
- Prefer single-line nested namespaces.
// my_component.h
#include <iostream>
class my_project::other_component;
namespace my_project::my_component {
// All declarations are within the namespace scope.
class MyComponent {
public:
void foo();
};
} // namespace my_project::my_component
// my_component.cpp
#include "my_component.h"
#include <vector>
namespace my_project::my_component {
// Definition of functions is within scope of the namespace.
void MyComponent::foo()
{
...
}
} // namespace my_project::my_component- Use anonymous namespaces for functions, variables, and custom types that are not referenced outside the file.
- Prefer anonymous namespaces over
static. - Do not use anonymous namespaces in headers.
// OK
namespace {
struct A {
int a;
};
int var;
void helper()
{
}
} // namespace
// Wrong
static int var;
static void helper()
{
}- Declare variables in the narrowest scope, as close to first use as possible.
- Initialize at declaration.
// OK
int jobs = num_jobs();
func(jobs);
for (int i = 0; i < jobs; ++i) {
}
// Wrong
int jobs;
int i;
...
func(jobs);
for (i = 0; i < jobs; ++i) {
}- Avoid any dynamic initialization of a global/static object that depends on another non-trivial global/static object in a different translation unit.
// OK - trivial
// config.cpp
constexpr int g_config = 1;
// logger.cpp
extern int g_config;
constexpr int g_logger_config = g_config;
// OK - initialization on first use
// config.cpp
const std::string &get_config()
{
static const std::string config = "project";
return config;
}
// logger.cpp
std::string get_prefix()
{
return get_config() + ":";
}
// Wrong - initialization order undefined
// config.cpp
const std::string g_config = "project";
// logger.cpp
const std::string g_prefix = g_config + ":";- Avoid global/file-local static objects whose destructors depend on other non-trivially destructible global/file-local static objects.
// OK - no external dependencies
// logger.cpp
struct Logger {
~Logger() {
std::cout << "shutdown\n";
}
};
Logger g_logger;
// Wrong - destruction order undefined
// config.cpp
std::string g_config = "project";
// logger.cpp
extern std::string g_config;
struct Logger {
~Logger() {
std::cout << "closing logs for " << g_config;
}
};
Logger g_logger;- Prefer
thread_localover other ways of defining thread-local data.
// OK
thread_local Parser parser;
// Wrong
std::unordered_map<std::thread::id, Parser> data;-
Avoid any dynamic initialization of a
thread_localobject that depends on another non-trivialthread_localobject in a different translation unit. -
Avoid
thread_localobjects whose destructors depend on other non-trivially destructiblethread_localobjects in different translation units.
- Avoid virtual calls in constructors.
- Keep constructors lightweight.
- Use factories or
init()for work that can fail (this project does not use exceptions as primary error flow).
// OK
Foo::Foo()
{
}
std::error_code Foo::init()
{
return connect_to_database();
}
// Wrong
Foo::Foo()
{
connect_to_database();
}- Mark single-argument constructors and conversion operators
explicit. - Copy/move constructors should stay non-
explicit. - Constructor with single std::initializer_list parameter should omit
explicit, in order to support copy-initialization (e.g.,MyType m = {1, 2};).
class Buffer {
public:
explicit Buffer(size_t size);
Buffer(const Buffer &buffer);
Buffer(Buffer &&buffer) noexcept;
Buffer(std::initializer_list<char> values);
explicit operator bool() const noexcept;
};- Make copy/move intent explicit (Rule of Five or Rule of Zero).
- Move operations should be
noexceptwhen defined.
class Socket {
public:
Socket() = default;
~Socket();
Socket(const Socket &) = delete;
Socket &operator=(const Socket &) = delete;
Socket(Socket &&) noexcept;
Socket &operator=(Socket &&) noexcept;
};- Use
structfor passive data with public fields and no invariants. - Use
classwhen methods enforce invariants.
// OK
struct Point {
double x, y;
};
// OK
class Circle {
public:
Circle(const Point ¢er, double radius);
int set_radius(double radius);
private:
Point center;
double radius;
};- Prefer named
structoverstd::pair/std::tuplewhen fields have meaning.
struct Range {
double low, high;
};- Use
overrideon all virtual function overrides. - Do not use
virtualwhen declaring an override. - Polymorphic base classes must have a virtual destructor.
// OK
class Base {
public:
virtual ~Base() = default;
virtual void process() = 0;
};
class Derived : public Base {
public:
void process() override;
};- Overload only when meaning matches built-in operators (
==,<<, etc.). - Do not overload
&&,||,,because evaluation order does not match semantics of the built-in operators. - Do not overload unary
&because the meaning depends on overload declaration visibility.
- Make class data members
privateunless they are constants.
- Keep declaration order:
public→protected→private. - Within each section, declare in this order:
- Types and type aliases (typedef, using, enum, nested structs and classes, and friend types).
- Static constants.
- Factory functions.
- Constructors and assignment operators.
- Destructor.
- All other functions (static and non-static member functions, and friend functions).
- All other data members (static and non-static).
- Omit empty sections (protected: if unused).
class Foo {
public:
using Id = int;
static constexpr int max_id = 100;
explicit Foo(Id id);
Id get_id() const noexcept;
private:
Id id;
};- Prefer output parameters over return values. Return
std::error_codefor success/failure status, or return data when the operation cannot fail. - Inputs: pass by value (cheap copies) or
const &(read-only). - Outputs: non-optional outputs use references; optional outputs use pointers.
- Place input arguments before output arguments.
// OK
std::error_code get_id(const std::string &name, Id &id);
// Wrong — output first
void get_id(Id &id, const std::string &name);- Keep functions focused; consider splitting around 40 lines.
- Extract helpers instead of deep nesting.
- Overload only when call sites are clear without knowing the exact overload.
- Prefer
std::string_viewover parallelstd::string/const char*overloads.
- Do not use default arguments for virtual functions.
- Use only for lambdas, template-heavy returns, or when it clearly improves readability.
template <typename T, typename U>
auto add(T a, U b) -> decltype(a + b);- Prefer single, clear owners; express transfers with
std::unique_ptr. - Use
std::shared_ptronly for shared ownership that is truly needed.
std::unique_ptr<Foo> make_foo();
void consume(std::unique_ptr<Foo> foo);- Do not use rvalue references casually in non-move APIs.
- Define friends in the same file when possible.
- Do not use C++ exceptions.
- Use
std::error_code,std::optional, orinit()/factories for expected failures. - Prefer third-party APIs that do not throw, or isolate throwing code at boundaries.
- Use
noexceptwhen it is useful for performance, especially move operations.
Socket(Socket &&) noexcept;
Socket &operator=(Socket &&) noexcept;- Avoid
typeidin production code; prefer virtual dispatch or visitors. - RTTI is acceptable in tests when necessary.
- Use C++ named casts; never C-style casts.
- Use
static_castfor safe conversions;reinterpret_castonly for low-level code.
// OK
auto x = static_cast<double>(value);
// Wrong
auto x = (double)value;- Prefer alternative facilities over streams for formatting, conversions, and logging due to performance overhead and limited control over formatting.
// OK
log_info("value={}", value);
// Wrong
std::cout << "value=" << value << "\n";
// OK
auto s = std::format("value={}", value);
// Wrong
std::stringstream ss;
ss << "value=" << value;- Prefer
++i/--iunless postfix semantics are required.
// OK
for (auto it = v.begin(); it != v.end(); ++it) {}- Use
conston parameters, locals (when helpful), and member functions that do not mutate logical state. - Do not use
constfor by-value parameters.
// OK
const int limit = 100;
const std::string &name() const;
void process(const Request &req);- Use
constexprfor compile-time constants and functions. - Use
constinitfor static/thread-local storage requiring constant initialization. - Use
constevalwhen evaluation must happen at compile time only. - Prefer
constexprover macros for constants.
// OK
constexpr int max_buffer = 4096;
constexpr int factorial(int n)
{
return n <= 1 ? 1 : n * factorial(n - 1);
}
constinit thread_local int tls_counter = 0;
consteval int square(int n)
{
return n * n;
}- Use
intfor small integers. - Use
<cstdint>(int32_t,uint64_t, etc.) when size matters. - Use
size_tfor sizes and container indices. - Omit the
std::prefix on standard library integer types.
uint64_t order_id;
size_t count = vec.size();- Use
floatanddoubleonly; avoidlong double. - Include a decimal point in literals (
1.0,0.5f).
- Use type-safe numeric formatting libraries like
std::formatinstead of theprintffamily of functions. - Use
uintptr_tto store memory addresses as integers. - Use portable floating point types; avoid
long double. - Use portable integer types; avoid
short,long, andlong long. - Use braced-initialization as needed to create 64-bit constants.
// OK
int64_t my_value { 0x123456789 };
uint64_t my_mask { uint64_t{ 3 } << 48 };- Avoid macros for constants and functions; use
constexprandinline. - Reserve macros for include guards and conditional compilation.
- When a macro is required:
- ALL_CAPS name with optional
_; same spacing as functions (no space after macro name in#define NAME(). - Parenthesize every argument and the full expansion.
- Multi-statement macros use
do { ... } while (0). - Prefer
#if defined(X)/#if !defined(X)over#ifdef/#ifndef. - Document every
#if/#elif/#else/#endifwith a trailing comment.
- ALL_CAPS name with optional
- Macro in header should have project name prefix.
// OK — macro formatting
#define SQUARE(x) ((x) * (x))
// Wrong
#define square(x) ((x) * (x))
#define SQUARE( x ) (( x ) * ( x ))// OK
#define MIN(x, y) ((x) < (y) ? (x) : (y))
// Wrong
#define MIN(x, y) x < y ? x : y// OK
#define SUM(x, y) ((x) + (y))
// Wrong — expansion not fully parenthesized
#define SUM(x, y) (x) + (y)// OK
#define DO_A_AND_B() \
do { \
do_a(); \
do_b(); \
} while (0)
// Wrong
#define DO_A_AND_B() \
{ \
do_a(); \
do_b(); \
}// OK
#if defined(XYZ)
#endif // defined(XYZ)
// Wrong
#ifdef XYZ
#endif // XYZ// OK
#if defined(XYZ)
#else // defined(XYZ)
#endif // defined(XYZ)
// Wrong
#if defined(XYZ)
#else
#endif// OK
#if defined(PROJECT_FEATURE_X)
#endif // defined(PROJECT_FEATURE_X)- Use
nullptrfor null pointers; never0orNULL. - Use
'\0'for the null character.
// OK
int *p = nullptr;
char c = '\0';- Prefer
sizeof(variable)oversizeof(Type). - Always parenthesize the operand:
sizeof(x), notsizeof x.
// OK
memset(&data, 0, sizeof(data));- Use type deduction only to make the code clearer or safer, and do not use it merely to avoid the inconvenience of writing an explicit type.
- Let the compiler deduce template arguments when clear.
- Use
autowhen type is obvious / not important.
// OK - it type is obvious, widget type is not
auto it = map.find(key);
Widget &widget = *it->second;- Avoid
autoreturn types on public APIs unless the type is obvious or template code requires it.
- Do not use auto parameters in non-lambda functions. Use named template parameters instead, because they are more explicit about the fact that the function is a template.
// OK
template <typename T>
void f(T arg)
{
}
// Wrong
void f(auto arg)
{
}- Use structured bindings when it improves readability.
// OK
const auto [iter, inserted] = map.insert({key, value});- Prefer explicit types over class template argument deduction.
// OK
std::vector<std::string> names = { "a", "b" };
// Wrong
std::vector names = { "a", "b" };- Use designated initializers only in the form that is compatible with the C++20 standard: with initializers in the same order as the corresponding fields appear in the struct definition.
// OK
Config cfg {
.host = "localhost",
.port = 8080,
};- Capture by reference only when lifetime is guaranteed.
- Avoid template metaprogramming.
- Prefer constraints over template metaprogramming.
- Not used in this project yet.
- Use only coroutine libraries that have been approved for project-wide use. Do not roll your own promise or awaitable types.
std::auto_ptr— usestd::unique_ptr.- Avoid deprecated facilities; follow compiler warnings.
- Prefer established libraries already in the project over ad-hoc implementations.
- Match third-party naming only at integration boundaries.
- Avoid compiler-specific extensions such as GCC's
__attribute__, intrinsic functions__builtin_popcountor SIMD,#pragma, inline assembly,__COUNTER__,__PRETTY_FUNCTION__, compound statement expressions (e.g.,foo = ({ int x; Bar(&x); x }), variable-length arrays,alloca(), the "Elvis Operator"a ? : b. - Wrap unavoidable extensions in portability macros or headers.
// OK — standard attribute
[[nodiscard]] int parse(const std::string &input);
// OK — standard library / portable code
int count = std::popcount(flags); // C++20
// Wrong — vendor builtin in application code
int count = __builtin_popcount(flags);
// Wrong — GCC/Clang-only attribute in public API
__attribute__((packed)) struct Header { int id; };
// Wrong — inline assembly without abstraction
asm("nop");
// OK — isolated platform layer (document why standard C++ is insufficient)
#if defined(__GNUC__)
#define FORCE_INLINE __attribute__((always_inline)) inline
#elif defined(_MSC_VER)
#define FORCE_INLINE __forceinline
#else
#define FORCE_INLINE inline
#endif- Prefer
usingovertypedef. - Type aliases at the appropriate scope — public API types in headers, local aliases in
.cpp.
// OK
using Id = std::uint64_t;- Always include
default. - Mark intentional fall-through with
[[fallthrough]].
// OK
switch (var) {
case 0:
do_job();
break;
default:
break;
}
// Wrong — default is missing
switch (var) {
case 0:
do_job();
break;
}
// OK — intentional fall-through
switch (state) {
case State::idle:
reset();
break;
case State::running:
tick();
[[fallthrough]];
case State::stopping:
shutdown();
break;
default:
break;
}- Clarity over cleverness.
- Names reflect meaning at their scope — short in tight loops, descriptive in public APIs.
- Project style: lowercase
snake_casefor functions, variables, and constants. - Do not use
_or__prefixes reserved for the implementation.
- Use
snake_case.h/snake_case.cpp.
- Type names (classes, structs, type aliases, enum types, and type template parameters) start with a capital letter and have a capital letter for each new word, with no underscores: MyClass, MyEnum.
// OK
class MyClass {
};
enum class MyEnum {
};- The same rule as Type Names
- Use
snake_case(all lowercase, with underscores between words) for the names of variables (including function parameters) and data members. - Do not encode type in the name (
i32_count).
// OK
class Session {
int user_id;
std::string display_name;
};- Use
snake_case. - Do not add trailing underscores
_for data members. - Do not add
m_prefix for data members.
- Use
snake_case.
- Use
snake_case.
// OK
constexpr int max_retries = 5;- Use
snake_casefor free functions and methods.
// OK
void process_request();
int compute_hash();- Use
snake_case.
// OK
namespace my_project {
} // namespace my_project- Use
snake_case.
// OK
enum class Color {
color_red,
color_green,
};- Use Type Names rules for naming type template parameters.
- Use rules Variable Names for naming non-type template parameters.
- Use ALL_CAPS with project prefix.
// OK
#define MYPROJECT_ASSERT(x) ...- Use Type Names rules.
- Add comments only for non-obvious functionality.
- Explain why, not what.
- Prefer
//for comments. - Use single space after
//.
- Every file includes the project license header.
- Do not add per-author ownership lines unless required by project policy.
- Limit lines to 100 characters.
// OK
if (has_valid_session() && session().user_id() == expected_user_id &&
session().expires_at() > Clock::now()) {
grant_access();
}
// Wrong
if (has_valid_session() && session().user_id() == expected_user_id && session().expires_at() > Clock::now()) {
grant_access();
}- Source files are UTF-8.
- Avoid non-ASCII in identifiers; acceptable in comments when needed (e.g. units).
// OK
constexpr double micros_per_second = 1e6; // µs- Use 4 spaces for indentation; never tabs.
- Use one additional indent level (4 spaces) for continuation lines.
- Do not align to the opening
(,", or parameter column. - Do not column-align variable assignments, function arguments, or comments.
// OK — function call continuation
log_info("parameter1: {} parameter2: {} parameter3: {} parameter4: {}", parameter1,
parameter2, parameter3, parameter4);
// Wrong
log_info("parameter1: {} parameter2: {} parameter3: {} parameter4: {}", parameter1,
parameter2, parameter3, parameter4);
// OK
int a = 1;
int abc = 2;
// Wrong
int a = 1;
int abc = 2;- Indentation is required for every opening bracket.
// OK
if (a) {
do_a();
} else {
do_b();
if (c) {
do_c();
}
}
// Wrong
if (a) {
do_a();
} else {
do_b();
if (c) {
do_c();
}
}- Return type on the same line as the function name.
- Opening brace on its own line for functions.
- Attach
*/&to the function name. - Empty parameter lists omit
voidunless C linkage requires it. - Do not align function prototypes or parameters.
- Add a forward declaration in a source file only if the function is used before it is defined.
// OK
void process()
{
}
// Wrong
void
process() {
}// OK
const char *my_func();
// Wrong
const char* my_func();// OK
void set(int32_t a);
const char *get();
// Wrong
void set(int32_t a);
const char *get();// OK
void my_function(int param1, char *param2, int param3, bool param4,
bool param5);
// Wrong
void my_function( int param1,
char *param2,
int param3,
bool param4,
bool param5 );// OK
void my_func();
// OK — C linkage
extern "C" {
void my_func(void);
}// OK
void my_func();
int main()
{
my_func();
return 0;
}
void my_func()
{
}
// Wrong
void func();
void my_func()
{
}
int main()
{
func();
return 0;
}- Short lambdas inline; multi-line lambdas indented like function bodies.
// OK
auto f = [](int x) { return x * 2; };
auto g = [&](const Request &r) {
validate(r);
submit(r);
};- Split long literals across lines using adjacent string literals, not
\continuation. - Align continuation literals with the opening quote of the first literal.
// OK
log_info("This is a long string that we want to print and is more than 100 chars long so we need "
"to split it");
// Wrong
log_info("This is a long string that we want to print and is more than 100 chars long so we need \
to split it");- Always include a decimal point to make the type explicit.
// OK
double ratio = 1.0 / 3.0;
float scale = 0.5f;- No space between function name and opening parenthesis.
- No space between opening parenthesis and first parameter.
- Single space after every comma.
int32_t a = sum(4, 3); // OK
int32_t a = sum (4, 3); // Wrong
int32_t a = sum( 4, 3 ); // Wrong
int32_t a = sum(4,3); // Wrong- Opening
{on the same line as the initializer. - Short lists inline; longer lists one element per line.
- Space inside braces:
{ 1, 2, 3 }.
// OK
std::vector<int> v = { 1, 2, 3 };
Node node = { 1, nullptr };
Node node = {
1,
nullptr,
};
// Wrong
Node node =
{
1,
nullptr,
};- Opening
{on the same line as the keyword (if,for,while,do,switch,else). - Always brace
if/for/whilebodies. else/else ifon the same line as the closing}of the preceding block.whileofdo-whileon the same line as the closing}ofdo.- The body of an
ifstatement must be on its own line (notif (fp) fclose(fp);). - Empty
for/whilebodies: use{}on the same line, not;.
size_t i;
for (i = 0; i < 5; i++) { // OK
}
for (i = 0; i < 5; i++){ // Wrong — space before {
}
for (i = 0; i < 5; i++) // Wrong — brace on next line
{
}
// OK
if (a) {
do_a();
}
if (c) {
for (int i = 0; i < 10; i++) {
do_a();
}
}
if (c) {
a = 12;
} else {
for (int i = 0; i < 10; i++) {
do_a();
}
}
// Wrong
if (a)
do_a();
if (c)
for (int i = 0; i < 10; i++)
do_a();
if (c)
a = 12;
else {
for (int i = 0; i < 10; i++)
do_a();
}
// OK
if (a) {
} else if (b) {
} else {
}
// Wrong
if (a) {
}
else {
}
if (a) {
}
else
{
}
// OK
do {
int32_t a;
a = do_a();
do_b(a);
} while (check());
// Wrong
do
{
} while (check());
do {
}
while (check());
// OK
if (fp) {
fclose(fp);
}
// Wrong
if (fp) fclose(fp);
// OK
for (i = 0; i < *p; i++) {}
// Wrong
for (i = 0; i < *p; i++);- Prefer
continue/returnover nested blocks. - Do not use
elseafterreturnin anifblock.
// OK — prefer continue over nesting
while (condition1) {
if (!condition2) {
continue;
}
if (condition3 && condition4) {
x = 10;
}
}
// Wrong
while (condition1) {
if (condition2) {
if (condition3) {
if (condition4) {
x = 10;
}
}
}
}
// OK — no else after return
if (condition) {
return;
}
do_work();
// Wrong
if (condition) {
return;
} else {
do_work();
}- Do not overuse parentheses.
// OK
if ((a & b) > 0 && c > 0 && d) {
}
// Wrong
if ((a & b) > 0 && (c > 0) && (d)) {
}
// Wrong
if ((my_func(a))) {
}
// Wrong
ptr = &(p->next);- Use parentheses when assigning in a condition expression of
if/for/while.
// OK
for (i = 0; (ret = my_func()); i++) {}
// Wrong
for (i = 0; ret = my_func(); i++) {}- Do not indent
case/defaultlabels. - Indent the body of each
case/defaultonce. - No space before
:; one space after:when the case body is on the same line. - If local variables are required inside a
case, wrap the body in{ }with{on the same line ascase; putbreakinside the braces.
// OK
switch (c) {
case 0:
do_a();
break;
case 1:
do_b();
break;
default:
break;
}
// Wrong
switch (c) {
case 0: do_a(); break;
case 1: do_b(); break;
default: break;
}
// Wrong
switch (c) {
case 0:
do_a();
break;
case 1:
do_b();
break;
default:
break;
}
// OK — case-local variables
switch (n) {
case 0: {
int32_t a, b;
char c;
a = 5;
break;
}
}
// Wrong
switch (n) {
case 0:
{
int32_t a;
break;
}
}
// Wrong — break must be inside the braces
switch (n) {
case 0: {
int32_t a;
}
break;
}- Attach
*and&to the name, not the type.
// OK
int *ptr;
const std::string &ref;
// Wrong
int* ptr;
const std::string& ref;- Do not compare variables against
0,false, ornullptr; use!instead. - Do not place the constant before the variable in a comparison.
size_t length = 5; // Counter variable
bool is_ok = false; // Boolean variable
void *ptr = nullptr; // Pointer variable
if (length == 5) // OK
if (!length) // OK
if (length) // OK
if (5 == length) // Wrong
if (length == 0) // Wrong
if (length != 0) // Wrong
if (is_ok) // OK
if (!is_ok) // OK
if (is_ok == true) // Wrong
if (is_ok == false) // Wrong
if (!ptr) // OK
if (ptr) // OK
if (ptr == nullptr) // Wrong
if (ptr != nullptr) // Wrong- Place logical
&&/||operators at the end when breaking line.
// OK
if (a > b &&
c == d &&
e && f) {
}- No parentheses around return values unless needed for clarity.
- Pointer functions return
nullptron failure. - Functions that can fail return
std::error_code; a default-constructed value means success. boolreturn types are for predicates (is_*,has_*) — returntrue/falsedirectly, not0/1. Do not useboolfor general success/failure status.- No bare
return;at the end ofvoidfunctions. - Exit early on errors and failed preconditions.
// OK
return result;
return (a > b) ? a : b; // parens OK for ternary
// Wrong
return (result);// OK
std::error_code open_file()
{
if (error) {
return std::make_error_code(std::errc::io_error);
}
return {};
}
std::error_code ec = open_file();
if (ec) {
log_error("open failed: {}", ec.message());
}
// OK
bool is_done()
{
if (done) {
return true;
}
return false;
}
void *my_alloc()
{
return is_ok ? ptr : nullptr;
}
// Wrong — bool for success/fail
bool my_func()
{
if (error) {
return false;
}
return true;
}// OK
void my_func()
{
}
// Wrong
void my_func()
{
return;
}// OK — exit early
void my_func()
{
if (!condition) {
return;
}
if (func1() < 0) {
return;
}
if (func2() < 0) {
return;
}
x = 10;
}
// Wrong — deep nesting
void my_func()
{
if (condition) {
if (func1()) {
if (func2()) {
x = 10;
}
}
}
}- Use brace initialization
{}to prevent narrowing conversions.
// OK — compile error
int x { 3.14 };
// Wrong — silent narrowing
int y = 3.14;- Do not indent code inside
#if/#elif/#else/#endifblocks.
// OK
#if defined(XYZ)
#if defined(ABC)
// do when ABC defined
#endif // defined(ABC)
#else // defined(XYZ)
// Do when XYZ not defined
#endif // defined(XYZ)
// Wrong
#if defined(XYZ)
#if defined(ABC)
// do when ABC defined
#endif // defined(ABC)
#else // defined(XYZ)
// Do when XYZ not defined
#endif // defined(XYZ)- Access specifiers aligned with
class; members indented 4 spaces. - Class opening brace on same line.
// OK
class Foo {
public:
explicit Foo(int id);
int get_id() const noexcept;
private:
int id;
};- Put
:and the initializer list on the same line as the constructor declaration when they fit within the column limit; otherwise put:at the end of the declaration line and wrap the initializers.
// OK
Foo::Foo(int id, std::string name) : id(id), name(std::move(name))
{
}- No extra indent inside namespaces; comment closing braces.
namespace my_project::net {
class Socket {};
} // namespace my_project::net- No trailing whitespace.
- Single space before and after assignment, binary, and ternary operators (
=,+,-,<,>,*,/,%,|,&,^,<=,>=,==,!=,?,:). - Do not align operators across lines.
int32_t a;
a = 3 + 4; // OK
for (a = 0; a < 5; a++) // OK
bits |= BIT5; // OK
a ? b : c // OK
a=3+4; // Wrong
a = 3+4; // Wrong
for (a=0;a<5;a++) // Wrong
bits|=BIT5; // Wrong
a?b:c // Wrong- No space after unary operators (
&,*,+,-,~,!). - No space around
.and->. - No space before postfix
++/--; no space after prefix++/--.
res = !x; // OK
ptr->x; // OK
++i; // OK
res = ! x; // Wrong
ptr -> x; // Wrong
++ i; // Wrong- Single space between
if/while/for/do/switchand the opening(.
// OK
if (condition)
while (condition)
for (init; condition; step)
do {} while (condition)
switch (var)
// Wrong
if(condition)
while(condition)
for(init;condition;step)
do {} while(condition)
switch(var)- No space in
static_cast<int>(value).
- One blank line between logical sections within a function (and between top-level definitions).
- No blank line at the start or end of a function body.
// OK
void func()
{
log_info("code block 1");
log_info("code block 2");
}
// Wrong — multiple consecutive blank lines
void func()
{
log_info("code block 1");
log_info("code block 2");
}- Match surrounding style in legacy code.
- Format code with clang-format using the project
.clang-formatin the repository root.