style-guide

Armchair Software Style Guide

This document defines Armchair Software’s house style for C++ and related build/script files. Use it as the default reference when writing or reviewing code in Armchair projects. When existing code differs, follow this guide and normalise touched code toward it.

0. Baseline Example

namespace example {

struct run_state {
  bool keep_running{true};
  unsigned int retry_limit{3};
};

auto process_item(item const &source, item &target)->bool {
  auto constexpr timeout{5s};
  if(source.age() > timeout) return false;
  if(!source.valid()) return false;

  target = item{
    .id{source.id},
    .name{source.name},
  };

  return true;
}

} // namespace example

Notes:

Table of Contents

1. Scope and Authority

1.1 Language scope

1.2 In-scope file types (.cpp, .h)

1.3 Out-of-scope code (third-party, vendored, generated)

1.4 Rule priority when conflicts occur

1.5 Migration policy for existing inconsistent code

2. Guide Authority

2.1 Source of truth

2.2 Ambiguity resolution

3. File and Project Structure

3.1 Project root layout conventions

3.2 Directory naming conventions

3.3 Module boundaries and folder ownership

3.4 Header/source pairing and location

3.5 Public vs private headers

3.6 Test/source placement conventions

3.7 Asset and non-code file placement

4. File Naming Conventions

4.1 .cpp and .h filename casing

Example:

world_manager.cpp
world_manager.h

4.2 Acronyms in names

4.3 Prefixes/suffixes (_manager, _renderer, _types, _forward)

4.4 Special filenames (main.cpp, version.h, protocol.h)

5. File Prologue and Includes

5.1 Header guard strategy (#pragma once vs include guards)

5.2 Include block ordering

5.3 Include grouping and blank lines

5.4 Forward declaration preferences vs includes

5.5 Include path style ("..." vs <...>)

6. Whitespace and Layout

6.1 Indentation width

6.2 Tabs vs spaces

6.3 Trailing whitespace policy

6.4 Consecutive blank line policy

6.5 Horizontal alignment policy

Example (avoid):

bool show_log_window{true};
bool show_console   {true};

Example (preferred):

bool show_log_window{true};
bool show_console{   true};

6.6 Line length policy and exceptions

6.7 Long statement wrapping and continuation indentation

Example:

logger << "DEBUG: on_close, event_type " << event_type
       << ", websocket " << event->socket
       << " closed" << (event->wasClean ? " cleanly" : ", not cleanly")
       << ", status: " << closeevent_code_to_text(event->code)
       << ", reason: \"" << event->reason << "\"";

6.8 Editor behaviour and defaults

7. Braces and Block Formatting

7.1 Function/class/struct brace placement

Example:

class network {
public:
  void run();
};

7.2 if/else brace and else placement

Example:

if(ready) {
  run_once();
} else {
  rebuild_state();
}

7.3 Loop/switch brace style

Example:

switch(mode) {
case mode_type::a:
  {
    int const value{1};
  }
  break;
case mode_type::b:
  execute();
  break;
}

7.4 Namespace brace style and closing comments

7.5 Lambda brace style

Example:

auto predicate{[&](int value) {
  if(value < 0) return false;
  return true;
}};

7.6 Single-line statement policy

Example:

if(ready) run_once();                    // allowed

if(ready) {                              // required when body is on next line
  run_once();
}

7.7 What does not get indented

7.8 Conditional indentation in special blocks

8. Spacing Rules

8.1 Control statement spacing (if( vs if ( etc.)

8.2 Function declaration/definition spacing

Example:

auto build_cache(config const &cfg)->cache;
void set_enabled(bool enabled);

void set_enabled(bool enabled) {
  enabled_state = enabled;
}

8.3 Operator spacing (arithmetic, assignment, comparison, logical)

8.4 Comma/semicolon/colon spacing

Example:

for(auto const &[name, value] : my_map) {
  switch(value.state) {
  case state::ready:
    consume(name, value);
    break;
  case state::skipped:
    continue;
  }
}

8.5 Pointer/reference spacing

8.6 Template angle bracket spacing

8.7 Cast spacing

8.8 Multi-line comma-separated list formatting

Example (avoid):

unsigned int get_named_node_index(
  std::vector<std::string> const &node_names,
  std::string_view const node_name,
  std::string_view const node_kind
) {
  return 0;
}

Example (preferred):

unsigned int get_named_node_index(std::vector<std::string> const &node_names,
                                  std::string_view const node_name,
                                  std::string_view const node_kind) {
  return 0;
}

9. Naming and Case Conventions

9.1 Namespace names

9.2 Type names (class/struct/enum/alias)

9.3 Function and method names

9.4 Variable names

9.5 Member names

9.6 Constants and constexpr names

9.7 Macro names

9.8 Enum value names

9.9 Template parameter names

9.10 Namespace usage rules

9.11 Language and spelling conventions

10. Declarations and Definitions

10.1 auto usage policy

10.2 Trailing return type style (auto f()->T)

10.3 Reference, const, and constexpr placement style

10.4 Declaration ordering inside classes

10.5 Initializer list formatting

10.6 Default member initialization

10.7 using aliases and typedef policy

10.8 static/type/constexpr/const ordering

Example:

static std::chrono::seconds constexpr update_interval{5s};
std::string const &name_ref{source.name};
constexpr auto has_flag(render_flags value, render_flags flag)->bool;

10.9 Integer signedness defaults

Example:

unsigned int retry_limit{3};
size_t const item_count{items.size()};
for(size_t i{0}; i != item_count; ++i) {
  process(items[i]);
}

10.10 Enum declarations and underlying types

Example:

enum class colour : uint8_t {
  red,
  green,
  blue,
};

10.11 Bitmask enum style

Example:

enum class render_flags : uint32_t {
  none = 0u,
  shadows = 1u << 0,
  bloom = 1u << 1,
  vsync = 1u << 2,
};

constexpr auto operator|(render_flags lhs, render_flags rhs)->render_flags {
  return static_cast<render_flags>(
      static_cast<uint32_t>(lhs) | static_cast<uint32_t>(rhs));
}

constexpr auto has_flag(render_flags value, render_flags flag)->bool {
  return (static_cast<uint32_t>(value) & static_cast<uint32_t>(flag)) != 0u;
}

11. Initialization and Expressions

11.1 Uniform initialization (house preference: universal braces)

Example:

int constexpr retries{3};
std::string const name{"sample"};
auto constexpr timeout{5s};

11.2 Assignment vs initialization distinctions

11.3 Designated initializers

Example:

result = item{
  .id{source.id},
  .name{source.name},
};

11.4 Literal formatting (1.0f, digit separators, chrono literals)

Example:

auto constexpr packet_mask{0b1000'0000'1010'0101u};
auto constexpr flags_mask{0b0011'1100u};
auto constexpr high_nibble{0b1111'0000u};
auto constexpr rgba_mask{0xFF'00'AA'55u};
auto const pattern{R"(^[A-Z]{3}\d{2}$)"};
using namespace std::string_view_literals;
auto constexpr status_text{"ready"sv};

11.5 Cast policy (static_cast, C-style cast restrictions)

11.6 Ternary operator formatting

11.7 Boolean expression clarity

Example (prefer):

bool const timed_out{now >= deadline};
bool const has_capacity{queue_size < max_queue_size};
if(timed_out || !has_capacity) return false;

Example (avoid):

if(!(now < deadline && (queue_size < max_queue_size || allow_overflow))) {
  return false;
}

11.8 Type deduction with brace initialization

Example:

auto constexpr timeout{5s};

auto build_config()->config {
  return {
    .retry_limit{3},
    .enabled{true},
  };
}

12. Control Flow Style

12.1 Early exit vs deep nesting

12.2 Condition complexity thresholds

12.3 switch style and exhaustiveness handling

12.4 Loop form preferences (for, range-for, while)

12.5 continue/break usage

13. Functions and APIs

13.1 Function size and responsibility

Example:

void process() {
  /// Validate input and fast-fail
  if(!ready) return;

  {                                      // setup phase
    setup_context const ctx{build_context()};
    state.setup(ctx);
  }

  for(size_t i{0}; i != items.size(); ++i) {
    state.update(items[i]);
  }
}

13.2 Parameter passing conventions

Example:

void set_count(unsigned int count);       // declaration

void set_count(unsigned int const count) { // definition
  cached_count = count;
}

void set_name(std::string name) {
  current_name = std::move(name);
}

template<typename T>
void push(T&& value) {
  storage.emplace_back(std::forward<T>(value));
}

13.3 Output parameters and return values

13.4 Overload design rules

13.5 [[nodiscard]] usage

13.6 noexcept guidance

13.7 Standard vs C library symbol usage

Example:

size_t const count{values.size()};
double const magnitude{std::abs(delta)};

13.8 Unused names (parameters and structured bindings)

Example:

void write_metric(std::string const &name, int /*sample_count*/) {
  metrics.emplace_back(name);
}

auto const &[key, unused_value]{entry}; // avoid
(void)unused_value;
use(key);

auto const &[key, _]{entry};            // preferred
use(key);

13.9 Function grouping and spacing

Example:

auto get_width() const->unsigned int {
  return width;
}
auto get_height() const->unsigned int {
  return height;
}
void set_width(unsigned int const value) {
  width = value;
}
void set_height(unsigned int const value) {
  height = value;
}

void rebuild_layout() {
  // separate function group
}

13.10 Declaration/definition ordering

14. Classes, Structs, and OOP

14.1 struct vs class usage

14.2 Access section ordering

Example:

class manager {
public:
  manager();

private:
  state current_state{};
};

14.3 Constructor and destructor conventions

14.4 Rule of 0/3/5 defaults

14.5 Inheritance formatting

14.6 Virtual and override conventions

15. Templates and Generic Code

15.1 Template declaration formatting

15.2 Constraints/concepts style

15.3 Specialization formatting

15.4 Generic naming conventions

15.5 Modern C++ feature preference

16. Lambdas and Callables

16.1 Capture style

16.2 Parameter and return formatting

Example:

auto tick{[]{
  return true;
}};

16.3 Multi-line lambda formatting

16.4 Callback conventions

17. Attributes, Macros, and Preprocessor

17.1 Standard attributes ([[nodiscard]], [[maybe_unused]], [[likely]])

17.2 Compiler attributes ([[gnu::pure]], etc.)

17.3 #define naming and scope

17.4 Conditional compilation (#if, #ifdef, #ifndef) style

17.5 Indentation and comments for preprocessor blocks

Example:

#ifdef DEBUG_WEBGPU
  logger << "DEBUG: Adapter info: " << adapter_info.description;
#endif // DEBUG_WEBGPU

17.6 Debug flag conventions

17.7 Warning suppression with diagnostics pragmas

Example:

#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wfloat-equal"
if(lhs == rhs) return true;              // byte-stable float compare is required by protocol
#pragma GCC diagnostic pop

18. Comments and Documentation

18.1 Line comments vs block comments

18.2 API doc comments (///)

Example:

void world_gui::draw(game_state &state) {
  /// Render the components of the world GUI for the current frame
  draw_windows(state);
}

/// @brief Parse command line arguments for the launcher
/// @param argc Number of arguments
/// @param argv Argument values
/// @return true if launch should continue
auto parse_args(int argc, char const *argv[])->bool {
  return argc > 0 && argv != nullptr;
}

18.3 Inline rationale comments

Example:

{                                        // temporary loading scope
  loader_context const ctx{build_loader_context()};
  run_loader(ctx);
}

18.4 TODO/FIXME/HACK conventions

18.5 Commented-out code policy

19. Error Handling and Diagnostics

19.1 Exception use and catch formatting

Example:

auto run_job_inner_try()->bool {          // avoid
  try {
    run();
    return true;
  } catch(std::exception const &e) {
    logger << "ERROR: run failed: " << e.what() << std::endl;
    return false;
  }
}

auto run_job()->bool try {                // preferred
  run();
  return true;
} catch(std::exception const &e) {
  logger << "ERROR: run failed: " << e.what() << std::endl;
  return false;
}

19.2 Error logging style

Example:

logger << "ERROR: upload failed for chunk " << chunk_id << std::endl;
bulk_log << line << '\n'; // allowed when non-flushing output is intentional

19.3 Assertions and defensive checks

Example:

assert(buffer_size > 0 && "buffer_size must be positive in upload path");

19.4 Failure message quality and consistency

20. CMakeLists.txt Style (Brief)

20.1 Indentation and list formatting

20.2 Condition and build-type block style

Example:

set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(EXCEPTION_HANDLING wasm CACHE STRING "Exception handling mode")
string(TOLOWER "${CMAKE_BUILD_TYPE}" build_type)

if(build_type STREQUAL "debug")
  message(STATUS "debug build")
else()
  message(FATAL_ERROR "invalid build type: ${CMAKE_BUILD_TYPE}")
endif()

20.3 Target declarations and grouping

Example:

add_executable(example_tool
  main.cpp
  app.cpp
  app.h
)

target_link_libraries(example_tool
  PRIVATE vectorstorm
  PRIVATE logstorm
)

21. Bash Script Style (Brief)

21.1 Shebang and safety defaults

21.2 Variable naming and quoting

21.3 Conditionals, loops, and spacing

21.4 Command failure handling

21.5 Logging output conventions

22. Revision Control

22.1 Line endings

22.2 Committed file permissions

22.3 Commit structure

22.4 Commit message style

23. Enforcement

23.1 Tools and automation

23.2 .editorconfig

23.3 .gitattributes

23.4 C++ autoformatters