move claude project files into git repo

This commit is contained in:
2026-08-02 15:40:54 +02:00
parent 2518f4f14a
commit 5fc30efde2
10 changed files with 2278 additions and 0 deletions

148
.claude/CLAUDE.md Normal file
View File

@@ -0,0 +1,148 @@
# CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
## Interaction
* ONLY modify code or other files if explicitly asked to do so
## Project Overview
Dota Factory is a single-player game that blends a Factorio-style factory builder with
DOTA-style wave defence. The player builds a factory on an asteroid — mining ores,
transporting materials over belts and splitters, and crafting through a config-defined
production tree — to supply shipyards that produce autonomous combat ships. Those ships
fight off endless enemy waves advancing from the right. See `docs/concept.md` for the full design.
## Project Structure
* the project root and the git repository root are the same directory
* project requirements can be found at `docs/requirements.md`
* architecture decisions can be found at `docs/architecture.md`
* game content design (ship/module roster, layout grids, footprint gating) can be found at `docs/content_design.md`
* replay/determinism design can be found at `docs/replay_design.md`
* balancing rules, targets, tuned numbers, process, and history live under `docs/balancing/`
Requirements carry stable `REQ-<AREA>-<NAME>` ids. They are cited throughout the code in
comments — when changing behavior, find the governing REQ id first and
keep the citation accurate.
## Coding Guidelines
* avoid duplicate code
* do not use the "auto" keyword
* use Qt utility data types (like QPoint, QVector3D, QString, etc.)
* wrap strings that appear in the UI with Qt's "tr()"
* use the EventManager/EventHandler instead of defining own signals and slots
* use std::optional if a variable can be "not set"
* start the name of a getter method with "get"
* don't use abbreviations, except very common ones ("s" for seconds, "min", "max", etc.)
* if a variable holds a value that has a unit or if a function returns a value that has a unit, append that unit to the name (e.g. "m_shipVelocity_mps", "getAcceleration_mpss()")
* always enclose scopes in braces
## Build
Requires CMake 3.14.4+, a C++17 compiler, and Qt 5 (developed against Qt 5.12.3,
MSVC 2017 x64; `Qt5_DIR` is cached in `build/CMakeCache.txt`). Needs Qt components
Widgets, Network, Multimedia, Charts, Svg, plus OpenGL.
External dependencies vendored under `src/external/`:
* **toml++** — reading TOML config files
* **tinyexpr** — evaluating formula strings from config files
* **EnTT** — entity registry backing the ship/station/debris simulation
* **Catch2** — test framework
Configure and build (a configured `build/` tree already exists):
```sh
cmake -S . -B build # configure (multi-config VS generator)
cmake --build build --config Debug # all targets
cmake --build build --config Debug --target DotaFactory_test
```
Targets: `DotaFactory` (app), `DotaFactory_lib`, `DotaFactory_ui`, `DotaFactory_test`,
`DotaFactory_balancing`. Executables land in `build/DotaFactory/<Config>/{app,balancing}/`.
**Adding a source file requires editing CMake.** Every directory under `src/` has its own
`CMakeLists.txt` listing files explicitly in `HDRS`/`SRCS` (or `TEST_FILES` for tests) —
there is no globbing. A new file that is not registered simply will not compile.
Config data is not copied: `CONFIG_DIR` is a compile definition pointing at
`bin/app/data/config` for the app and balancing tool, and `bin/test/data/config` for
tests (a separate fixture set). On Windows the build also junctions `bin/*/data` into the
output directories and copies the Qt DLLs.
Run the app: `build/DotaFactory/Debug/app/DotaFactory.exe`, optionally
`--replay <file>` for view-only playback of a recorded run.
## Tests
Catch2, single executable, links `lib` only — no QApplication, no display.
```sh
build/DotaFactory/Debug/app/DotaFactory_test.exe # all
build/DotaFactory/Debug/app/DotaFactory_test.exe "[belt],[building]" # by tag
build/DotaFactory/Debug/app/DotaFactory_test.exe "BeltSystem: *" # by name pattern
build/DotaFactory/Debug/app/DotaFactory_test.exe --reporter compact
```
Common tags: `[building] [belt] [behavior] [blueprint] [modules] [config] [wave] [combat]
[replay] [determinism] [ship] [debris] [threat] [unlock]`.
`src/test/SimulationTestAccess.h` is a friend-struct backdoor to `Simulation`'s private
mutators; tests use it instead of duplicating the command path. It lives under `src/test`
and is deliberately off the lib/ui/app include path.
## Verification Tools
Python scripts in `tools/` read the real configs and are the first check
after config edits (see `docs/balancing/process.md`):
* `verify_recipes.py` — recipe-tree closure, visuals coverage, orphan items
* `verify_layouts.py` — module footprint gating per hull layout
* `threat_report.py` — item/module/ship threat values, ratios, belt feasibility
The `DotaFactory_balancing` target runs parallel arena simulations from
`bin/balancing/data/balancing.toml` for combat-stat tuning.
## Architecture
See `docs/architecture.md` for the full write-up. Highlights and the
invariants that are easy to break:
* Strict simulation/presentation split, enforced at the CMake target level: `lib`
(sim + config, Qt Core/Gui only — no QtWidgets), `ui` (QtWidgets + QOpenGLWidget),
`app` (thin main), `test` (Catch2 against `lib`).
* Fixed 30 Hz tick simulation, 60 FPS render, accumulator-driven; game speed is a
tick-rate multiplier. All sim quantities are in ticks, never wall-clock seconds.
* The tick order in `Simulation::tick()` is load-bearing for determinism — see the
Tick Order section of `architecture.md` before reordering systems.
* **Command chokepoint:** every sim mutation during play flows through
`Simulation::apply(const Command&)` (see `sim/Command.h`, `CommandManager`), so runs can
be recorded and replayed. Commands reference stable ids (`BuildingId`, tile coords,
choice indices) — never raw `entt::entity` handles. UI code must not call sim mutators
directly. Determinism is checksummed (`StateChecksum`) and covered by
`DeterminismTest` / `ReplayPlaybackTest`.
* Config is loaded once at startup, formulas compiled once via tinyexpr, immutable
afterwards; malformed config aborts startup rather than failing mid-game. Restart
reloads config from disk (REQ-CFG-RELOAD).
* **The sim uses EnTT for ships, stations, debris, and module child entities**, wrapped by
`core/EntityAdmin` (registry, factory methods, `forEach<Ts...>` views). Components live
in `lib/ecs/component/`, systems in `lib/ecs/system/`. Note: `architecture.md`'s
"Ships" and "Why Not ECS" sections still describe the earlier
`std::optional<Component>` design and are stale on this point; the code is authoritative.
Buildings and the belt subsystem stay outside the entity model.
* Ship AI is score-based, not fixed-priority: `AiSystem` runs evaluate → select → execute
phases over per-behavior evaluator/executor pairs in `lib/ecs/system/ai/`. Evaluators and
executors never mutate the world; world mutation lives in `CombatSystem`,
`SalvagerSystem`, `RepairSystem`, `MovementIntentSystem`.
* Belt subsystem is behind a narrow port-level interface (`tryPutItem` / `tryTakeItem` /
`clearTiles` / `tick` / `forEachVisualItem`); per-tile implementation now, swappable
later. No other system asks "what is on tile X".
* All inter-widget and sim→UI communication goes through the `EventManager`/`EventHandler`
singleton in `lib/eventsystem/` (events in `lib/eventsystem/event/`). The sim itself
stays free of EventManager for determinism — it buffers `BeamFiredEvent`s in a vector
that the UI drains each frame and re-emits.
* State-change events are *refresh signals*, not carriers of truth: a widget re-reads the
value from `Simulation` rather than caching the event payload.

View File

@@ -0,0 +1,49 @@
{
"permissions": {
"allow": [
"Bash(cmake --build . --config Debug --target DotaFactory_test)",
"Bash(cmake ..)",
"Bash(cmake --build . --config Debug --target DotaFactory)",
"Bash(D:/projects/2026_dota_factory/dota_factory/build/DotaFactory/Debug/app/DotaFactory_test.exe *)",
"Bash(./DotaFactory/Debug/app/DotaFactory_test.exe *)",
"Bash(cmake --build .)",
"Bash(xargs grep *)",
"Bash(cmake --build build)",
"Bash(./build/DotaFactory/Debug/app/DotaFactory_test.exe *)",
"Bash(cmake --build D:/projects/2026_dota_factory/dota_factory/build)",
"Bash(D:/projects/2026_dota_factory/dota_factory/build/DotaFactory/Debug/app/DotaFactory_test.exe [wave],[combat] *)",
"Bash(\"D:/projects/2026_dota_factory/dota_factory/build/DotaFactory/Debug/app/DotaFactory_test.exe\" \"[wave],[combat]\")",
"Bash(\"D:/projects/2026_dota_factory/dota_factory/build/DotaFactory/Debug/app/DotaFactory_test.exe\")",
"Bash(cmake --build D:/projects/2026_dota_factory/dota_factory/build --config Debug)",
"Bash(cmake --build D:/projects/2026_dota_factory/dota_factory/build --config Debug --target DotaFactory_test)",
"Bash(cmake *)",
"Bash(/d/projects/2026_dota_factory/dota_factory/build/DotaFactory/Debug/app/DotaFactory_test.exe *)",
"PowerShell(cmake *)",
"PowerShell(& \"D:\\\\projects\\\\2026_dota_factory\\\\dota_factory\\\\build\\\\DotaFactory\\\\Debug\\\\app\\\\DotaFactory_test.exe\" --reporter compact 2>&1 | Select-Object -Last 30)",
"PowerShell(& \"D:\\\\projects\\\\2026_dota_factory\\\\dota_factory\\\\build\\\\DotaFactory\\\\Debug\\\\app\\\\DotaFactory_test.exe\" --reporter compact 2>&1 | Select-Object -Last 5)",
"Bash(Get-ChildItem *)",
"PowerShell(ls -Recurse \"D:\\\\projects\\\\2026_dota_factory\" -Directory | Select-Object -First 30 | ForEach-Object { $_.FullName })",
"PowerShell(cmd /c \"dir D:\\\\projects\\\\2026_dota_factory\")",
"PowerShell(cmd /c \"dir D:\\\\projects\\\\2026_dota_factory\\\\dota_factory\")",
"PowerShell(& \"D:\\\\projects\\\\2026_dota_factory\\\\dota_factory\\\\build\\\\DotaFactory\\\\Debug\\\\app\\\\DotaFactory_test.exe\" 2>&1)",
"PowerShell(& \"D:\\\\projects\\\\2026_dota_factory\\\\dota_factory\\\\build\\\\DotaFactory\\\\Debug\\\\app\\\\DotaFactory_test.exe\" 2>&1 | Select-Object -Last 10)",
"PowerShell(& \"D:\\\\projects\\\\2026_dota_factory\\\\dota_factory\\\\build\\\\DotaFactory\\\\Debug\\\\app\\\\DotaFactory_test.exe\" 2>&1 | Select-Object -Last 5)",
"Bash(Select-Object -First 20)",
"Bash(cd *)",
"Bash(dir D:\\\\projects\\\\2026_dota_factory *)",
"Bash(dir D:\\\\projects\\\\2026_dota_factory\\\\dota_factory *)",
"Bash(dir D:\\\\projects\\\\2026_dota_factory\\\\dota_factory\\\\src *)",
"Bash(dir D:\\\\projects\\\\2026_dota_factory\\\\dota_factory\\\\src\\\\lib *)",
"Bash(grep -i \"\\\\.h$\")",
"Bash(cd /d \"D:\\\\projects\\\\2026_dota_factory\")",
"Bash(dir /s /b *.cpp *.h)",
"Bash(grep -E \"\\\\.\\(h|cpp\\)$\")",
"Bash(ls *.sln)",
"Bash(ls CMakeCache.txt)",
"PowerShell(& \"D:\\\\projects\\\\2026_dota_factory\\\\build\\\\DotaFactory\\\\Debug\\\\app\\\\DotaFactory_test.exe\" \"[behavior]\" 2>&1 | Select-Object -Last 30)",
"PowerShell(& \"D:\\\\projects\\\\2026_dota_factory\\\\build\\\\DotaFactory\\\\Debug\\\\app\\\\DotaFactory_test.exe\" 2>&1 | Select-Object -Last 4)",
"Bash(grep -iE \"error C[0-9]|error:|FAILED|\\\\.exe$\")",
"Bash(./DotaFactory_test.exe)"
]
}
}

View File

@@ -0,0 +1,28 @@
---
name: bug
description: Investigate a reported bug, find and explain its root cause, and propose a fix — without implementing anything
argument-hint: <description of the buggy behavior>
disable-model-invocation: true
---
A bug has been reported:
$ARGUMENTS
Investigate it and propose a solution. **Do not implement anything** — no edits, no new files, no fixes applied. The goal of this pass is understanding and a proposal the user can approve first.
Work through it like this:
1. **Pin down expected vs. actual.** Restate what the behavior should be and what it actually is. If the report is ambiguous about the conditions that trigger it, note your assumptions explicitly.
2. **Find the relevant code.** Search for the subsystem(s) involved (Grep/Glob, then Read the actual files). Don't reason from memory or from names alone — read the implementation that runs in this case.
3. **Trace the real execution path.** Follow the data/control flow step by step for the specific failing scenario. For the tick-based simulation, that means tracing the relevant systems in tick order, including the per-tick progress/cap arithmetic where it matters. Use the project's actual constants (tick rate, belt speed, etc.) rather than hand-waving.
4. **State the root cause precisely.** Name the exact mechanism, citing `file:line`. Explain *why* it produces the observed symptom — connect the cause to the visible effect concretely (e.g. "single-slot output serializes to one item per full-tile traversal, so items land ~1 tile apart"). Confirm it explains the specific trigger conditions in the report.
5. **Propose a solution.** Describe the change and where it would go (`file:line`), reusing existing patterns in the codebase. If the symptom has more than one contributing path, say so. If the fix involves a design or balance trade-off (correctness vs. throughput, lossless vs. capped, a visual side effect, etc.), surface it as a decision for the user — give a recommendation, but ask before assuming which behavior they want.
6. **Stop and hand back.** End with the proposal and any open questions. Offer to implement (and to add tests) only once the user has chosen a direction.
Keep the write-up grounded in what the code actually does — quote the lines that matter. Adhere to the repository's coding guidelines and architecture notes (see `.claude/CLAUDE.md`) when describing any proposed change.

View File

@@ -0,0 +1,91 @@
---
name: C++ Pro
description: Expert C++ developer specializing in modern C++20/23, systems programming, and high-performance computing. Masters template metaprogramming, zero-overhead abstractions, and low-level optimization with emphasis on safety and efficiency.
triggers:
- C++
- C++17
- C++20
- C++23
- modern C++
- template metaprogramming
- systems programming
- performance optimization
- SIMD
- memory management
- CMake
role: specialist
scope: implementation
output-format: code
---
# C++ Pro
Senior C++ developer with deep expertise in modern C++20/23, systems programming, high-performance computing, and zero-overhead abstractions.
## Role Definition
You are a senior C++ engineer with 15+ years of systems programming experience. You specialize in modern C++20/23, template metaprogramming, performance optimization, and building production-grade systems with emphasis on safety, efficiency, and maintainability. You follow C++ Core Guidelines and leverage cutting-edge language features.
## When to Use This Skill
- Building high-performance C++ applications
- Implementing template metaprogramming solutions
- Optimizing memory-critical systems
- Developing concurrent and parallel algorithms
- Creating custom allocators and memory pools
- Systems programming and embedded development
## Core Workflow
1. **Analyze architecture** - Review build system, compiler flags, performance requirements
2. **Design with concepts** - Create type-safe interfaces using C++20 concepts
3. **Implement zero-cost** - Apply RAII, constexpr, and zero-overhead abstractions
4. **Verify quality** - Run sanitizers, static analysis, and performance benchmarks
5. **Optimize** - Profile, measure, and apply targeted optimizations
## Reference Guide
Load detailed guidance based on context:
| Topic | Reference | Load When |
|-------|-----------|-----------|
| Modern C++ Features | `references/modern-cpp.md` | C++20/23 features, concepts, ranges, coroutines |
| Template Metaprogramming | `references/templates.md` | Variadic templates, SFINAE, type traits, CRTP |
| Memory & Performance | `references/memory-performance.md` | Allocators, SIMD, cache optimization, move semantics |
| Concurrency | `references/concurrency.md` | Atomics, lock-free structures, thread pools, coroutines |
| Build & Tooling | `references/build-tooling.md` | CMake, sanitizers, static analysis, testing |
## Constraints
### MUST DO
- Follow C++ Core Guidelines
- Use concepts for template constraints
- Apply RAII universally
- Do not use `auto`
- Prefer `std::unique_ptr` and `std::shared_ptr`
- Write const-correct code
- Use forward declarations in header files if possible
- Use descriptive functions names and variable names instead of writing comments
### MUST NOT DO
- Use raw `new`/`delete` (prefer smart pointers)
- Ignore compiler warnings
- Use C-style casts (use static_cast, etc.)
- Mix exception and error code patterns inconsistently
- Write non-const-correct code
- Use `using namespace std` in headers
- Ignore undefined behavior
- Skip move semantics for expensive types
- Write lots of comments
## Output Templates
When implementing C++ features, provide:
1. Header file with interfaces and templates
2. Implementation file (when needed)
3. CMakeLists.txt updates (if applicable)
4. Test file demonstrating usage
## Knowledge Reference
C++20/23, concepts, ranges, coroutines, modules, template metaprogramming, SFINAE, type traits, CRTP, smart pointers, custom allocators, move semantics, RAII, SIMD, atomics, lock-free programming, CMake, Conan, sanitizers, clang-tidy, cppcheck, Catch2, GoogleTest

View File

@@ -0,0 +1,443 @@
# Build Systems and Tooling
> Reference for: C++ Pro
> Load when: CMake, sanitizers, static analysis, testing frameworks, CI/CD
## Modern CMake
```cmake
cmake_minimum_required(VERSION 3.20)
project(MyProject VERSION 1.0.0 LANGUAGES CXX)
# Set C++ standard
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)
# Export compile commands for tools
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
# Compiler warnings
if(MSVC)
add_compile_options(/W4 /WX)
else()
add_compile_options(-Wall -Wextra -Wpedantic -Werror)
endif()
# Create library target
add_library(mylib
src/mylib.cpp
include/mylib.h
)
target_include_directories(mylib
PUBLIC
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
$<INSTALL_INTERFACE:include>
PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}/src
)
target_compile_features(mylib PUBLIC cxx_std_20)
# Create executable
add_executable(myapp src/main.cpp)
target_link_libraries(myapp PRIVATE mylib)
# Dependencies with FetchContent
include(FetchContent)
FetchContent_Declare(
fmt
GIT_REPOSITORY https://github.com/fmtlib/fmt.git
GIT_TAG 10.1.1
)
FetchContent_MakeAvailable(fmt)
target_link_libraries(mylib PUBLIC fmt::fmt)
# Testing
enable_testing()
add_subdirectory(tests)
# Install rules
include(GNUInstallDirs)
install(TARGETS mylib myapp
EXPORT MyProjectTargets
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
)
install(DIRECTORY include/
DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}
)
```
## Sanitizers
```cmake
# AddressSanitizer (ASan) - memory errors
set(CMAKE_CXX_FLAGS_ASAN
"-g -O1 -fsanitize=address -fno-omit-frame-pointer"
CACHE STRING "Flags for ASan build"
)
# UndefinedBehaviorSanitizer (UBSan)
set(CMAKE_CXX_FLAGS_UBSAN
"-g -O1 -fsanitize=undefined -fno-omit-frame-pointer"
CACHE STRING "Flags for UBSan build"
)
# ThreadSanitizer (TSan) - data races
set(CMAKE_CXX_FLAGS_TSAN
"-g -O1 -fsanitize=thread -fno-omit-frame-pointer"
CACHE STRING "Flags for TSan build"
)
# MemorySanitizer (MSan) - uninitialized reads
set(CMAKE_CXX_FLAGS_MSAN
"-g -O1 -fsanitize=memory -fno-omit-frame-pointer"
CACHE STRING "Flags for MSan build"
)
# Usage: cmake -DCMAKE_BUILD_TYPE=ASAN ..
```
## Static Analysis
```yaml
# .clang-tidy configuration
---
Checks: >
*,
-fuchsia-*,
-google-*,
-llvm-*,
-modernize-use-trailing-return-type,
-readability-identifier-length
WarningsAsErrors: '*'
CheckOptions:
- key: readability-identifier-naming.ClassCase
value: CamelCase
- key: readability-identifier-naming.FunctionCase
value: lower_case
- key: readability-identifier-naming.VariableCase
value: lower_case
- key: readability-identifier-naming.ConstantCase
value: UPPER_CASE
- key: readability-identifier-naming.MemberCase
value: lower_case
- key: readability-identifier-naming.MemberSuffix
value: '_'
- key: modernize-use-nullptr.NullMacros
value: 'NULL'
```
```bash
# Run clang-tidy
clang-tidy src/*.cpp -p build/
# Run cppcheck
cppcheck --enable=all --std=c++20 --suppress=missingInclude src/
# Run include-what-you-use
include-what-you-use -std=c++20 src/main.cpp
```
## Testing with Catch2
```cpp
#include <catch2/catch_test_macros.hpp>
#include <catch2/benchmark/catch_benchmark.hpp>
#include "mylib.h"
TEST_CASE("Vector operations", "[vector]") {
std::vector<int> vec{1, 2, 3};
SECTION("push_back") {
vec.push_back(4);
REQUIRE(vec.size() == 4);
REQUIRE(vec.back() == 4);
}
SECTION("pop_back") {
vec.pop_back();
REQUIRE(vec.size() == 2);
REQUIRE(vec.back() == 2);
}
}
TEST_CASE("Exception handling", "[exceptions]") {
REQUIRE_THROWS_AS(risky_function(), std::runtime_error);
REQUIRE_THROWS_WITH(risky_function(), "error message");
}
TEST_CASE("Floating point", "[math]") {
REQUIRE_THAT(compute_value(),
Catch::Matchers::WithinAbs(3.14, 0.01));
}
BENCHMARK("Vector creation") {
return std::vector<int>(1000);
};
BENCHMARK("Vector fill") {
std::vector<int> vec(1000);
for (int i = 0; i < 1000; ++i) {
vec[i] = i;
}
return vec;
};
```
## Testing with GoogleTest
```cpp
#include <gtest/gtest.h>
#include <gmock/gmock.h>
#include "calculator.h"
class CalculatorTest : public ::testing::Test {
protected:
void SetUp() override {
calc = std::make_unique<Calculator>();
}
void TearDown() override {
calc.reset();
}
std::unique_ptr<Calculator> calc;
};
TEST_F(CalculatorTest, Addition) {
EXPECT_EQ(calc->add(2, 3), 5);
EXPECT_EQ(calc->add(-1, 1), 0);
}
TEST_F(CalculatorTest, Division) {
EXPECT_DOUBLE_EQ(calc->divide(10, 2), 5.0);
EXPECT_THROW(calc->divide(10, 0), std::invalid_argument);
}
// Parameterized tests
class AdditionTest : public ::testing::TestWithParam<std::tuple<int, int, int>> {};
TEST_P(AdditionTest, ValidAddition) {
auto [a, b, expected] = GetParam();
Calculator calc;
EXPECT_EQ(calc.add(a, b), expected);
}
INSTANTIATE_TEST_SUITE_P(
AdditionSuite,
AdditionTest,
::testing::Values(
std::make_tuple(1, 2, 3),
std::make_tuple(-1, -2, -3),
std::make_tuple(0, 0, 0)
)
);
// Mock objects
class MockDatabase : public Database {
public:
MOCK_METHOD(void, connect, (const std::string&), (override));
MOCK_METHOD(std::string, query, (const std::string&), (override));
MOCK_METHOD(void, disconnect, (), (override));
};
TEST(ServiceTest, UsesDatabase) {
MockDatabase mock_db;
EXPECT_CALL(mock_db, connect("localhost"))
.Times(1);
EXPECT_CALL(mock_db, query("SELECT *"))
.WillOnce(::testing::Return("result"));
Service service(mock_db);
service.process();
}
```
## Performance Profiling
```cpp
// Benchmark with Google Benchmark
#include <benchmark/benchmark.h>
static void BM_VectorPush(benchmark::State& state) {
for (auto _ : state) {
std::vector<int> vec;
for (int i = 0; i < state.range(0); ++i) {
vec.push_back(i);
}
benchmark::DoNotOptimize(vec);
}
}
BENCHMARK(BM_VectorPush)->Range(8, 8<<10);
static void BM_VectorReserve(benchmark::State& state) {
for (auto _ : state) {
std::vector<int> vec;
vec.reserve(state.range(0));
for (int i = 0; i < state.range(0); ++i) {
vec.push_back(i);
}
benchmark::DoNotOptimize(vec);
}
}
BENCHMARK(BM_VectorReserve)->Range(8, 8<<10);
BENCHMARK_MAIN();
```
```bash
# Profiling with perf (Linux)
perf record -g ./myapp
perf report
# Profiling with Instruments (macOS)
instruments -t "Time Profiler" ./myapp
# Valgrind callgrind
valgrind --tool=callgrind ./myapp
kcachegrind callgrind.out.*
# Memory profiling
valgrind --tool=massif ./myapp
ms_print massif.out.*
```
## Conan Package Manager
```python
# conanfile.txt
[requires]
fmt/10.1.1
spdlog/1.12.0
catch2/3.4.0
[generators]
CMakeDeps
CMakeToolchain
[options]
fmt:header_only=True
```
```cmake
# CMakeLists.txt with Conan
cmake_minimum_required(VERSION 3.20)
project(MyProject)
find_package(fmt REQUIRED)
find_package(spdlog REQUIRED)
find_package(Catch2 REQUIRED)
add_executable(myapp src/main.cpp)
target_link_libraries(myapp
PRIVATE
fmt::fmt
spdlog::spdlog
)
add_executable(tests test/main.cpp)
target_link_libraries(tests
PRIVATE
Catch2::Catch2WithMain
)
```
```bash
# Install dependencies
conan install . --output-folder=build --build=missing
cd build
cmake .. -DCMAKE_TOOLCHAIN_FILE=conan_toolchain.cmake
cmake --build .
```
## CI/CD with GitHub Actions
```yaml
# .github/workflows/ci.yml
name: CI
on: [push, pull_request]
jobs:
build:
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
compiler: [gcc, clang, msvc]
build_type: [Debug, Release]
steps:
- uses: actions/checkout@v3
- name: Install dependencies
run: |
pip install conan
conan install . --output-folder=build --build=missing
- name: Configure
run: |
cmake -B build -DCMAKE_BUILD_TYPE=${{ matrix.build_type }}
- name: Build
run: cmake --build build --config ${{ matrix.build_type }}
- name: Test
run: ctest --test-dir build -C ${{ matrix.build_type }}
sanitizers:
runs-on: ubuntu-latest
strategy:
matrix:
sanitizer: [asan, ubsan, tsan]
steps:
- uses: actions/checkout@v3
- name: Build with sanitizer
run: |
cmake -B build -DCMAKE_BUILD_TYPE=${{ matrix.sanitizer }}
cmake --build build
- name: Run tests
run: ctest --test-dir build
static-analysis:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Run clang-tidy
run: |
cmake -B build -DCMAKE_EXPORT_COMPILE_COMMANDS=ON
clang-tidy src/*.cpp -p build/
- name: Run cppcheck
run: cppcheck --enable=all --error-exitcode=1 src/
```
## Quick Reference
| Tool | Purpose | Command |
|------|---------|---------|
| CMake | Build system | `cmake -B build && cmake --build build` |
| Conan | Package manager | `conan install . --build=missing` |
| ASan | Memory errors | `-fsanitize=address` |
| UBSan | Undefined behavior | `-fsanitize=undefined` |
| TSan | Data races | `-fsanitize=thread` |
| clang-tidy | Static analysis | `clang-tidy src/*.cpp` |
| cppcheck | Static analysis | `cppcheck --enable=all src/` |
| Catch2 | Unit testing | `TEST_CASE("name") { REQUIRE(...); }` |
| GoogleTest | Unit testing | `TEST(Suite, Name) { EXPECT_EQ(...); }` |
| Google Benchmark | Performance | `BENCHMARK(func)->Range(...)` |
| Valgrind | Memory profiler | `valgrind --tool=memcheck ./app` |

View File

@@ -0,0 +1,440 @@
# Concurrency and Parallel Programming
> Reference for: C++ Pro
> Load when: Atomics, lock-free structures, thread pools, parallel algorithms, coroutines
## Atomics and Memory Ordering
```cpp
#include <atomic>
#include <thread>
// Basic atomics
std::atomic<int> counter{0};
std::atomic<bool> flag{false};
// Memory ordering
void producer(std::atomic<int>& data, std::atomic<bool>& ready) {
data.store(42, std::memory_order_relaxed);
ready.store(true, std::memory_order_release); // Release barrier
}
void consumer(std::atomic<int>& data, std::atomic<bool>& ready) {
while (!ready.load(std::memory_order_acquire)) { // Acquire barrier
std::this_thread::yield();
}
int value = data.load(std::memory_order_relaxed);
}
// Compare-and-swap
bool try_acquire_lock(std::atomic<bool>& lock) {
bool expected = false;
return lock.compare_exchange_strong(expected, true,
std::memory_order_acquire,
std::memory_order_relaxed);
}
// Fetch-and-add
int increment_counter(std::atomic<int>& counter) {
return counter.fetch_add(1, std::memory_order_relaxed);
}
```
## Lock-Free Data Structures
```cpp
#include <atomic>
#include <memory>
// Lock-free stack
template<typename T>
class LockFreeStack {
struct Node {
T data;
Node* next;
Node(const T& value) : data(value), next(nullptr) {}
};
std::atomic<Node*> head_{nullptr};
public:
void push(const T& value) {
Node* new_node = new Node(value);
new_node->next = head_.load(std::memory_order_relaxed);
while (!head_.compare_exchange_weak(new_node->next, new_node,
std::memory_order_release,
std::memory_order_relaxed)) {
// Retry with updated head
}
}
bool pop(T& result) {
Node* old_head = head_.load(std::memory_order_relaxed);
while (old_head &&
!head_.compare_exchange_weak(old_head, old_head->next,
std::memory_order_acquire,
std::memory_order_relaxed)) {
// Retry
}
if (old_head) {
result = old_head->data;
delete old_head; // Note: ABA problem exists
return true;
}
return false;
}
};
// Lock-free queue (single producer, single consumer)
template<typename T, size_t Size>
class SPSCQueue {
std::array<T, Size> buffer_;
alignas(64) std::atomic<size_t> head_{0};
alignas(64) std::atomic<size_t> tail_{0};
public:
bool push(const T& item) {
size_t head = head_.load(std::memory_order_relaxed);
size_t next_head = (head + 1) % Size;
if (next_head == tail_.load(std::memory_order_acquire)) {
return false; // Queue full
}
buffer_[head] = item;
head_.store(next_head, std::memory_order_release);
return true;
}
bool pop(T& item) {
size_t tail = tail_.load(std::memory_order_relaxed);
if (tail == head_.load(std::memory_order_acquire)) {
return false; // Queue empty
}
item = buffer_[tail];
tail_.store((tail + 1) % Size, std::memory_order_release);
return true;
}
};
```
## Thread Pool
```cpp
#include <thread>
#include <queue>
#include <mutex>
#include <condition_variable>
#include <functional>
#include <future>
class ThreadPool {
std::vector<std::thread> workers_;
std::queue<std::function<void()>> tasks_;
std::mutex queue_mutex_;
std::condition_variable condition_;
bool stop_ = false;
public:
ThreadPool(size_t num_threads) {
for (size_t i = 0; i < num_threads; ++i) {
workers_.emplace_back([this] {
while (true) {
std::function<void()> task;
{
std::unique_lock<std::mutex> lock(queue_mutex_);
condition_.wait(lock, [this] {
return stop_ || !tasks_.empty();
});
if (stop_ && tasks_.empty()) {
return;
}
task = std::move(tasks_.front());
tasks_.pop();
}
task();
}
});
}
}
~ThreadPool() {
{
std::unique_lock<std::mutex> lock(queue_mutex_);
stop_ = true;
}
condition_.notify_all();
for (auto& worker : workers_) {
worker.join();
}
}
template<typename F, typename... Args>
auto enqueue(F&& f, Args&&... args)
-> std::future<typename std::invoke_result_t<F, Args...>> {
using return_type = typename std::invoke_result_t<F, Args...>;
auto task = std::make_shared<std::packaged_task<return_type()>>(
std::bind(std::forward<F>(f), std::forward<Args>(args)...)
);
std::future<return_type> result = task->get_future();
{
std::unique_lock<std::mutex> lock(queue_mutex_);
if (stop_) {
throw std::runtime_error("enqueue on stopped ThreadPool");
}
tasks_.emplace([task]() { (*task)(); });
}
condition_.notify_one();
return result;
}
};
```
## Parallel STL Algorithms
```cpp
#include <algorithm>
#include <execution>
#include <vector>
#include <numeric>
void parallel_algorithms_demo() {
std::vector<int> vec(1'000'000);
std::iota(vec.begin(), vec.end(), 0);
// Parallel sort
std::sort(std::execution::par, vec.begin(), vec.end());
// Parallel for_each
std::for_each(std::execution::par_unseq, vec.begin(), vec.end(),
[](int& x) { x *= 2; });
// Parallel transform
std::vector<int> result(vec.size());
std::transform(std::execution::par, vec.begin(), vec.end(),
result.begin(), [](int x) { return x * x; });
// Parallel reduce
int sum = std::reduce(std::execution::par, vec.begin(), vec.end());
// Parallel transform_reduce (map-reduce)
int sum_of_squares = std::transform_reduce(
std::execution::par,
vec.begin(), vec.end(),
0,
std::plus<>(),
[](int x) { return x * x; }
);
}
```
## Synchronization Primitives
```cpp
#include <mutex>
#include <shared_mutex>
#include <condition_variable>
// Mutex types
std::mutex mtx;
std::recursive_mutex rec_mtx;
std::timed_mutex timed_mtx;
std::shared_mutex shared_mtx;
// RAII locks
void exclusive_access() {
std::lock_guard<std::mutex> lock(mtx);
// Critical section
}
void unique_lock_example() {
std::unique_lock<std::mutex> lock(mtx);
// Can unlock and relock
lock.unlock();
// Do some work
lock.lock();
}
// Reader-writer lock
class SharedData {
mutable std::shared_mutex mutex_;
std::string data_;
public:
std::string read() const {
std::shared_lock<std::shared_mutex> lock(mutex_);
return data_;
}
void write(std::string new_data) {
std::unique_lock<std::shared_mutex> lock(mutex_);
data_ = std::move(new_data);
}
};
// Condition variable
class Queue {
std::queue<int> queue_;
std::mutex mutex_;
std::condition_variable cv_;
public:
void push(int value) {
{
std::lock_guard<std::mutex> lock(mutex_);
queue_.push(value);
}
cv_.notify_one();
}
int pop() {
std::unique_lock<std::mutex> lock(mutex_);
cv_.wait(lock, [this] { return !queue_.empty(); });
int value = queue_.front();
queue_.pop();
return value;
}
};
// std::scoped_lock - multiple mutexes
std::mutex mtx1, mtx2;
void transfer(Account& from, Account& to, int amount) {
std::scoped_lock lock(from.mutex, to.mutex); // Deadlock-free
from.balance -= amount;
to.balance += amount;
}
```
## Async and Futures
```cpp
#include <future>
// std::async
auto future = std::async(std::launch::async, []() {
return expensive_computation();
});
// Get result (blocks until ready)
auto result = future.get();
// Promise and future
void producer(std::promise<int> promise) {
int value = compute_value();
promise.set_value(value);
}
void consumer(std::future<int> future) {
int value = future.get();
}
std::promise<int> promise;
std::future<int> future = promise.get_future();
std::thread producer_thread(producer, std::move(promise));
std::thread consumer_thread(consumer, std::move(future));
// Packaged task
std::packaged_task<int(int, int)> task([](int a, int b) {
return a + b;
});
std::future<int> task_future = task.get_future();
std::thread task_thread(std::move(task), 5, 3);
int sum = task_future.get(); // 8
task_thread.join();
```
## Coroutine-Based Concurrency
```cpp
#include <coroutine>
#include <optional>
// Async task coroutine
template<typename T>
struct AsyncTask {
struct promise_type {
std::optional<T> value;
std::exception_ptr exception;
AsyncTask get_return_object() {
return AsyncTask{
std::coroutine_handle<promise_type>::from_promise(*this)
};
}
std::suspend_never initial_suspend() { return {}; }
std::suspend_always final_suspend() noexcept { return {}; }
void return_value(T v) {
value = std::move(v);
}
void unhandled_exception() {
exception = std::current_exception();
}
};
std::coroutine_handle<promise_type> handle;
AsyncTask(std::coroutine_handle<promise_type> h) : handle(h) {}
~AsyncTask() { if (handle) handle.destroy(); }
T get() {
if (!handle.done()) {
handle.resume();
}
if (handle.promise().exception) {
std::rethrow_exception(handle.promise().exception);
}
return *handle.promise().value;
}
};
// Usage
AsyncTask<int> async_compute() {
co_return 42;
}
```
## Quick Reference
| Primitive | Use Case | Performance |
|-----------|----------|-------------|
| std::atomic | Simple shared state | Lock-free |
| std::mutex | Exclusive access | Kernel call |
| std::shared_mutex | Read-heavy workload | Better than mutex |
| Lock-free structures | High contention | Best throughput |
| Thread pool | Task parallelism | Avoid thread overhead |
| Parallel STL | Data parallelism | Automatic scaling |
| std::async | Simple async tasks | Thread pool |
| Coroutines | Async I/O | Minimal overhead |
## Memory Ordering Guide
| Ordering | Guarantees | Use Case |
|----------|-----------|----------|
| relaxed | No synchronization | Counters |
| acquire | Load barrier | Consumer |
| release | Store barrier | Producer |
| acq_rel | Both | RMW operations |
| seq_cst | Total order | Default |

View File

@@ -0,0 +1,400 @@
# Memory Management & Performance
> Reference for: C++ Pro
> Load when: Custom allocators, SIMD, cache optimization, move semantics, memory pools
## Smart Pointers
```cpp
#include <memory>
// unique_ptr - exclusive ownership
auto create_resource() {
return std::make_unique<Resource>("data");
}
// shared_ptr - reference counting
std::shared_ptr<Data> shared = std::make_shared<Data>(42);
std::weak_ptr<Data> weak = shared; // Non-owning reference
// Custom deleters
auto file_deleter = [](FILE* fp) { if (fp) fclose(fp); };
std::unique_ptr<FILE, decltype(file_deleter)> file(
fopen("data.txt", "r"),
file_deleter
);
// enable_shared_from_this
class Node : public std::enable_shared_from_this<Node> {
public:
std::shared_ptr<Node> get_shared() {
return shared_from_this();
}
};
```
## Custom Allocators
```cpp
#include <memory>
#include <vector>
// Pool allocator for fixed-size objects
template<typename T, size_t PoolSize = 1024>
class PoolAllocator {
struct Block {
alignas(T) std::byte data[sizeof(T)];
Block* next;
};
Block pool_[PoolSize];
Block* free_list_ = nullptr;
public:
using value_type = T;
PoolAllocator() {
// Initialize free list
for (size_t i = 0; i < PoolSize - 1; ++i) {
pool_[i].next = &pool_[i + 1];
}
pool_[PoolSize - 1].next = nullptr;
free_list_ = &pool_[0];
}
T* allocate(size_t n) {
if (n != 1 || !free_list_) {
throw std::bad_alloc();
}
Block* block = free_list_;
free_list_ = free_list_->next;
return reinterpret_cast<T*>(block->data);
}
void deallocate(T* p, size_t n) {
if (n != 1) return;
Block* block = reinterpret_cast<Block*>(p);
block->next = free_list_;
free_list_ = block;
}
};
// Usage
std::vector<int, PoolAllocator<int>> vec;
// Arena allocator - bump allocator
class Arena {
std::byte* buffer_;
size_t size_;
size_t offset_ = 0;
public:
Arena(size_t size) : size_(size) {
buffer_ = new std::byte[size];
}
~Arena() {
delete[] buffer_;
}
template<typename T>
T* allocate(size_t n = 1) {
size_t alignment = alignof(T);
size_t space = size_ - offset_;
void* ptr = buffer_ + offset_;
if (std::align(alignment, sizeof(T) * n, ptr, space)) {
offset_ = size_ - space + sizeof(T) * n;
return static_cast<T*>(ptr);
}
throw std::bad_alloc();
}
void reset() {
offset_ = 0;
}
};
```
## Move Semantics
```cpp
#include <utility>
#include <algorithm>
class Buffer {
size_t size_;
char* data_;
public:
// Constructor
Buffer(size_t size) : size_(size), data_(new char[size]) {}
// Destructor
~Buffer() { delete[] data_; }
// Copy constructor
Buffer(const Buffer& other) : size_(other.size_), data_(new char[size_]) {
std::copy(other.data_, other.data_ + size_, data_);
}
// Copy assignment
Buffer& operator=(const Buffer& other) {
if (this != &other) {
delete[] data_;
size_ = other.size_;
data_ = new char[size_];
std::copy(other.data_, other.data_ + size_, data_);
}
return *this;
}
// Move constructor
Buffer(Buffer&& other) noexcept
: size_(other.size_), data_(other.data_) {
other.size_ = 0;
other.data_ = nullptr;
}
// Move assignment
Buffer& operator=(Buffer&& other) noexcept {
if (this != &other) {
delete[] data_;
size_ = other.size_;
data_ = other.data_;
other.size_ = 0;
other.data_ = nullptr;
}
return *this;
}
};
// Perfect forwarding
template<typename T>
void wrapper(T&& arg) {
process(std::forward<T>(arg)); // Preserves lvalue/rvalue
}
```
## SIMD Optimization
```cpp
#include <immintrin.h> // AVX/AVX2
#include <cstring>
// Vectorized sum using AVX2
float simd_sum(const float* data, size_t size) {
__m256 sum_vec = _mm256_setzero_ps();
size_t i = 0;
// Process 8 floats at a time
for (; i + 8 <= size; i += 8) {
__m256 vec = _mm256_loadu_ps(&data[i]);
sum_vec = _mm256_add_ps(sum_vec, vec);
}
// Horizontal sum
alignas(32) float temp[8];
_mm256_store_ps(temp, sum_vec);
float result = 0.0f;
for (int j = 0; j < 8; ++j) {
result += temp[j];
}
// Handle remaining elements
for (; i < size; ++i) {
result += data[i];
}
return result;
}
// Vectorized multiply-add
void fma_operation(float* result, const float* a, const float* b,
const float* c, size_t size) {
for (size_t i = 0; i + 8 <= size; i += 8) {
__m256 va = _mm256_loadu_ps(&a[i]);
__m256 vb = _mm256_loadu_ps(&b[i]);
__m256 vc = _mm256_loadu_ps(&c[i]);
// result[i] = a[i] * b[i] + c[i]
__m256 vr = _mm256_fmadd_ps(va, vb, vc);
_mm256_storeu_ps(&result[i], vr);
}
}
```
## Cache-Friendly Design
```cpp
// Structure of Arrays (SoA) - better cache locality
struct ParticlesAoS {
struct Particle {
float x, y, z;
float vx, vy, vz;
};
std::vector<Particle> particles;
};
struct ParticlesSoA {
std::vector<float> x, y, z;
std::vector<float> vx, vy, vz;
void update_positions(float dt) {
// All x coordinates are contiguous - better cache usage
for (size_t i = 0; i < x.size(); ++i) {
x[i] += vx[i] * dt;
y[i] += vy[i] * dt;
z[i] += vz[i] * dt;
}
}
};
// Cache line padding to avoid false sharing
struct alignas(64) CacheLinePadded {
std::atomic<int> counter;
char padding[64 - sizeof(std::atomic<int>)];
};
// Prefetching
void process_with_prefetch(const int* data, size_t size) {
for (size_t i = 0; i < size; ++i) {
// Prefetch data for next iteration
if (i + 8 < size) {
__builtin_prefetch(&data[i + 8], 0, 1);
}
// Process current data
process(data[i]);
}
}
```
## Memory Pool
```cpp
#include <vector>
#include <memory>
template<typename T, size_t ChunkSize = 256>
class MemoryPool {
struct Chunk {
alignas(T) std::byte data[sizeof(T) * ChunkSize];
};
std::vector<std::unique_ptr<Chunk>> chunks_;
std::vector<T*> free_list_;
size_t current_chunk_offset_ = ChunkSize;
public:
T* allocate() {
if (!free_list_.empty()) {
T* ptr = free_list_.back();
free_list_.pop_back();
return ptr;
}
if (current_chunk_offset_ >= ChunkSize) {
chunks_.push_back(std::make_unique<Chunk>());
current_chunk_offset_ = 0;
}
Chunk* chunk = chunks_.back().get();
T* ptr = reinterpret_cast<T*>(
&chunk->data[sizeof(T) * current_chunk_offset_++]
);
return ptr;
}
void deallocate(T* ptr) {
free_list_.push_back(ptr);
}
template<typename... Args>
T* construct(Args&&... args) {
T* ptr = allocate();
new (ptr) T(std::forward<Args>(args)...);
return ptr;
}
void destroy(T* ptr) {
ptr->~T();
deallocate(ptr);
}
};
```
## Copy Elision and RVO
```cpp
// Return Value Optimization (RVO)
std::vector<int> create_vector() {
std::vector<int> vec{1, 2, 3, 4, 5};
return vec; // RVO applies, no copy/move
}
// Named Return Value Optimization (NRVO)
std::string build_string(bool condition) {
std::string result;
if (condition) {
result = "condition true";
} else {
result = "condition false";
}
return result; // NRVO may apply
}
// Guaranteed copy elision (C++17)
struct NonMovable {
NonMovable() = default;
NonMovable(const NonMovable&) = delete;
NonMovable(NonMovable&&) = delete;
};
NonMovable create() {
return NonMovable{}; // Guaranteed no copy/move in C++17
}
auto obj = create(); // OK in C++17
```
## Alignment and Memory Layout
```cpp
#include <cstddef>
// Control alignment
struct alignas(64) CacheAligned {
int data[16];
};
// Check alignment
static_assert(alignof(CacheAligned) == 64);
// Aligned allocation
void* aligned_alloc_wrapper(size_t alignment, size_t size) {
void* ptr = nullptr;
if (posix_memalign(&ptr, alignment, size) != 0) {
throw std::bad_alloc();
}
return ptr;
}
// Placement new with alignment
alignas(32) std::byte buffer[sizeof(Data)];
Data* obj = new (buffer) Data();
obj->~Data(); // Manual destruction needed
```
## Quick Reference
| Technique | Use Case | Benefit |
|-----------|----------|---------|
| Smart Pointers | Ownership management | Memory safety |
| Move Semantics | Avoid copies | Performance |
| Custom Allocators | Specialized allocation | Speed + control |
| SIMD | Parallel computation | 4-8x speedup |
| SoA Layout | Sequential access | Cache efficiency |
| Memory Pools | Frequent alloc/dealloc | Reduced fragmentation |
| Alignment | SIMD/cache optimization | Performance |
| RVO/NRVO | Return objects | Zero-copy |

View File

@@ -0,0 +1,307 @@
# Modern C++20/23 Features
> Reference for: C++ Pro
> Load when: Using C++20/23 features, concepts, ranges, coroutines, modules
## Concepts and Constraints
```cpp
#include <concepts>
// Define custom concepts
template<typename T>
concept Numeric = std::integral<T> || std::floating_point<T>;
template<typename T>
concept Hashable = requires(T a) {
{ std::hash<T>{}(a) } -> std::convertible_to<std::size_t>;
};
template<typename T>
concept Container = requires(T c) {
typename T::value_type;
typename T::iterator;
{ c.begin() } -> std::same_as<typename T::iterator>;
{ c.end() } -> std::same_as<typename T::iterator>;
{ c.size() } -> std::convertible_to<std::size_t>;
};
// Use concepts for function constraints
template<Numeric T>
T add(T a, T b) {
return a + b;
}
// Concept-based overloading
template<std::integral T>
void process(T value) {
std::cout << "Processing integer: " << value << '\n';
}
template<std::floating_point T>
void process(T value) {
std::cout << "Processing float: " << value << '\n';
}
```
## Ranges and Views
```cpp
#include <ranges>
#include <vector>
#include <algorithm>
// Ranges-based algorithms
std::vector<int> numbers = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
// Filter, transform, take - all lazy evaluation
auto result = numbers
| std::views::filter([](int n) { return n % 2 == 0; })
| std::views::transform([](int n) { return n * n; })
| std::views::take(3);
// Copy to vector only when needed
std::vector<int> materialized(result.begin(), result.end());
// Custom range adaptor
auto is_even = [](int n) { return n % 2 == 0; };
auto square = [](int n) { return n * n; };
auto pipeline = std::views::filter(is_even)
| std::views::transform(square);
auto processed = numbers | pipeline;
```
## Coroutines
```cpp
#include <coroutine>
#include <iostream>
#include <memory>
// Generator coroutine
template<typename T>
struct Generator {
struct promise_type {
T current_value;
auto get_return_object() {
return Generator{std::coroutine_handle<promise_type>::from_promise(*this)};
}
std::suspend_always initial_suspend() { return {}; }
std::suspend_always final_suspend() noexcept { return {}; }
std::suspend_always yield_value(T value) {
current_value = value;
return {};
}
void return_void() {}
void unhandled_exception() { std::terminate(); }
};
std::coroutine_handle<promise_type> handle;
Generator(std::coroutine_handle<promise_type> h) : handle(h) {}
~Generator() { if (handle) handle.destroy(); }
bool move_next() {
handle.resume();
return !handle.done();
}
T current_value() {
return handle.promise().current_value;
}
};
// Usage
Generator<int> fibonacci() {
int a = 0, b = 1;
while (true) {
co_yield a;
auto next = a + b;
a = b;
b = next;
}
}
// Async coroutine
#include <future>
struct Task {
struct promise_type {
Task get_return_object() {
return Task{std::coroutine_handle<promise_type>::from_promise(*this)};
}
std::suspend_never initial_suspend() { return {}; }
std::suspend_never final_suspend() noexcept { return {}; }
void return_void() {}
void unhandled_exception() {}
};
std::coroutine_handle<promise_type> handle;
};
Task async_operation() {
std::cout << "Starting async work\n";
co_await std::suspend_always{};
std::cout << "Resuming async work\n";
}
```
## Three-Way Comparison (Spaceship)
```cpp
#include <compare>
struct Point {
int x, y;
// Auto-generate all comparison operators
auto operator<=>(const Point&) const = default;
};
// Custom spaceship operator
struct Version {
int major, minor, patch;
std::strong_ordering operator<=>(const Version& other) const {
if (auto cmp = major <=> other.major; cmp != 0) return cmp;
if (auto cmp = minor <=> other.minor; cmp != 0) return cmp;
return patch <=> other.patch;
}
bool operator==(const Version& other) const = default;
};
```
## Designated Initializers
```cpp
struct Config {
std::string host = "localhost";
int port = 8080;
bool ssl_enabled = false;
int timeout_ms = 5000;
};
// C++20 designated initializers
Config cfg {
.host = "example.com",
.port = 443,
.ssl_enabled = true
// timeout_ms uses default
};
```
## Modules (C++20)
```cpp
// math.cppm - module interface
export module math;
export namespace math {
template<typename T>
T add(T a, T b) {
return a + b;
}
class Calculator {
public:
int multiply(int a, int b);
};
}
// Implementation
module math;
int math::Calculator::multiply(int a, int b) {
return a * b;
}
// Usage in other files
import math;
int main() {
auto result = math::add(5, 3);
math::Calculator calc;
auto product = calc.multiply(4, 7);
}
```
## constexpr Enhancements
```cpp
#include <string>
#include <vector>
#include <algorithm>
// C++20: constexpr std::string and std::vector
constexpr auto compute_at_compile_time() {
std::vector<int> vec{1, 2, 3, 4, 5};
std::ranges::reverse(vec);
return vec[0]; // Returns 5
}
constexpr int value = compute_at_compile_time();
// constexpr virtual functions (C++20)
struct Base {
constexpr virtual int get_value() const { return 42; }
constexpr virtual ~Base() = default;
};
struct Derived : Base {
constexpr int get_value() const override { return 100; }
};
```
## std::format (C++20)
```cpp
#include <format>
#include <iostream>
int main() {
std::string msg = std::format("Hello, {}!", "World");
// Positional arguments
auto text = std::format("{1} {0}", "World", "Hello");
// Formatting options
double pi = 3.14159265;
auto formatted = std::format("Pi: {:.2f}", pi); // "Pi: 3.14"
// Custom types
struct Point { int x, y; };
}
// Custom formatter
template<>
struct std::formatter<Point> {
constexpr auto parse(format_parse_context& ctx) {
return ctx.begin();
}
auto format(const Point& p, format_context& ctx) const {
return std::format_to(ctx.out(), "({}, {})", p.x, p.y);
}
};
```
## Quick Reference
| Feature | C++17 | C++20 | C++23 |
|---------|-------|-------|-------|
| Concepts | - | ✓ | ✓ |
| Ranges | - | ✓ | ✓ |
| Coroutines | - | ✓ | ✓ |
| Modules | - | ✓ | ✓ |
| Spaceship | - | ✓ | ✓ |
| std::format | - | ✓ | ✓ |
| std::expected | - | - | ✓ |
| std::print | - | - | ✓ |
| Deducing this | - | - | ✓ |

View File

@@ -0,0 +1,360 @@
# Template Metaprogramming
> Reference for: C++ Pro
> Load when: Variadic templates, SFINAE, type traits, CRTP, compile-time programming
## Variadic Templates
```cpp
#include <iostream>
#include <utility>
// Fold expressions (C++17)
template<typename... Args>
auto sum(Args... args) {
return (args + ...); // Unary right fold
}
template<typename... Args>
void print(Args&&... args) {
((std::cout << args << ' '), ...); // Binary left fold
std::cout << '\n';
}
// Recursive variadic template
template<typename T>
void log(T&& value) {
std::cout << value << '\n';
}
template<typename T, typename... Args>
void log(T&& first, Args&&... rest) {
std::cout << first << ", ";
log(std::forward<Args>(rest)...);
}
// Parameter pack expansion
template<typename... Types>
struct TypeList {
static constexpr size_t size = sizeof...(Types);
};
template<typename... Args>
auto make_tuple_advanced(Args&&... args) {
return std::tuple<std::decay_t<Args>...>(std::forward<Args>(args)...);
}
```
## SFINAE and if constexpr
```cpp
#include <type_traits>
// SFINAE with std::enable_if (older style)
template<typename T>
std::enable_if_t<std::is_integral_v<T>, T>
double_value(T value) {
return value * 2;
}
template<typename T>
std::enable_if_t<std::is_floating_point_v<T>, T>
double_value(T value) {
return value * 2.0;
}
// Modern: if constexpr (C++17)
template<typename T>
auto process(T value) {
if constexpr (std::is_integral_v<T>) {
return value * 2;
} else if constexpr (std::is_floating_point_v<T>) {
return value * 2.0;
} else {
return value;
}
}
// Detection idiom
template<typename T, typename = void>
struct has_serialize : std::false_type {};
template<typename T>
struct has_serialize<T, std::void_t<decltype(std::declval<T>().serialize())>>
: std::true_type {};
template<typename T>
constexpr bool has_serialize_v = has_serialize<T>::value;
// Use with if constexpr
template<typename T>
void save(const T& obj) {
if constexpr (has_serialize_v<T>) {
obj.serialize();
} else {
// Default serialization
}
}
```
## Type Traits
```cpp
#include <type_traits>
// Custom type traits
template<typename T>
struct remove_all_pointers {
using type = T;
};
template<typename T>
struct remove_all_pointers<T*> {
using type = typename remove_all_pointers<T>::type;
};
template<typename T>
using remove_all_pointers_t = typename remove_all_pointers<T>::type;
// Conditional types
template<bool Condition, typename T, typename F>
struct conditional_type {
using type = T;
};
template<typename T, typename F>
struct conditional_type<false, T, F> {
using type = F;
};
// Compile-time type selection
template<size_t N>
struct best_integral_type {
using type = std::conditional_t<N <= 8, uint8_t,
std::conditional_t<N <= 16, uint16_t,
std::conditional_t<N <= 32, uint32_t, uint64_t>>>;
};
// Check for member functions
template<typename T, typename = void>
struct has_reserve : std::false_type {};
template<typename T>
struct has_reserve<T, std::void_t<decltype(std::declval<T>().reserve(size_t{}))>>
: std::true_type {};
```
## CRTP (Curiously Recurring Template Pattern)
```cpp
// Static polymorphism with CRTP
template<typename Derived>
class Shape {
public:
double area() const {
return static_cast<const Derived*>(this)->area_impl();
}
void draw() const {
static_cast<const Derived*>(this)->draw_impl();
}
};
class Circle : public Shape<Circle> {
double radius_;
public:
Circle(double r) : radius_(r) {}
double area_impl() const {
return 3.14159 * radius_ * radius_;
}
void draw_impl() const {
std::cout << "Drawing circle\n";
}
};
class Rectangle : public Shape<Rectangle> {
double width_, height_;
public:
Rectangle(double w, double h) : width_(w), height_(h) {}
double area_impl() const {
return width_ * height_;
}
void draw_impl() const {
std::cout << "Drawing rectangle\n";
}
};
// CRTP for mixin capabilities
template<typename Derived>
class Printable {
public:
void print() const {
std::cout << static_cast<const Derived*>(this)->to_string() << '\n';
}
};
class User : public Printable<User> {
std::string name_;
public:
User(std::string name) : name_(std::move(name)) {}
std::string to_string() const {
return "User: " + name_;
}
};
```
## Template Template Parameters
```cpp
#include <vector>
#include <list>
#include <deque>
// Template template parameter
template<typename T, template<typename, typename> class Container>
class Stack {
Container<T, std::allocator<T>> data_;
public:
void push(const T& value) {
data_.push_back(value);
}
T pop() {
T value = data_.back();
data_.pop_back();
return value;
}
size_t size() const {
return data_.size();
}
};
// Usage with different containers
Stack<int, std::vector> vector_stack;
Stack<int, std::deque> deque_stack;
Stack<int, std::list> list_stack;
```
## Compile-Time Computation
```cpp
#include <array>
// Compile-time factorial
constexpr int factorial(int n) {
return n <= 1 ? 1 : n * factorial(n - 1);
}
constexpr int fact_5 = factorial(5); // Computed at compile time
// Compile-time prime checking
constexpr bool is_prime(int n) {
if (n < 2) return false;
for (int i = 2; i * i <= n; ++i) {
if (n % i == 0) return false;
}
return true;
}
// Generate compile-time array of primes
template<size_t N>
constexpr auto generate_primes() {
std::array<int, N> primes{};
int count = 0;
int candidate = 2;
while (count < N) {
if (is_prime(candidate)) {
primes[count++] = candidate;
}
++candidate;
}
return primes;
}
constexpr auto first_10_primes = generate_primes<10>();
```
## Expression Templates
```cpp
// Lazy evaluation with expression templates
template<typename E>
class VecExpression {
public:
double operator[](size_t i) const {
return static_cast<const E&>(*this)[i];
}
size_t size() const {
return static_cast<const E&>(*this).size();
}
};
class Vec : public VecExpression<Vec> {
std::vector<double> data_;
public:
Vec(size_t n) : data_(n) {}
double operator[](size_t i) const { return data_[i]; }
double& operator[](size_t i) { return data_[i]; }
size_t size() const { return data_.size(); }
// Evaluate expression template
template<typename E>
Vec& operator=(const VecExpression<E>& expr) {
for (size_t i = 0; i < size(); ++i) {
data_[i] = expr[i];
}
return *this;
}
};
// Binary operation expression
template<typename E1, typename E2>
class VecSum : public VecExpression<VecSum<E1, E2>> {
const E1& lhs_;
const E2& rhs_;
public:
VecSum(const E1& lhs, const E2& rhs) : lhs_(lhs), rhs_(rhs) {}
double operator[](size_t i) const {
return lhs_[i] + rhs_[i];
}
size_t size() const { return lhs_.size(); }
};
// Operator overload
template<typename E1, typename E2>
VecSum<E1, E2> operator+(const VecExpression<E1>& lhs,
const VecExpression<E2>& rhs) {
return VecSum<E1, E2>(static_cast<const E1&>(lhs),
static_cast<const E2&>(rhs));
}
// Usage: a = b + c + d (no temporaries created!)
```
## Quick Reference
| Technique | Use Case | Performance |
|-----------|----------|-------------|
| Variadic Templates | Variable arguments | Zero overhead |
| SFINAE | Conditional compilation | Compile-time |
| if constexpr | Type-based branching | Zero overhead |
| CRTP | Static polymorphism | No vtable cost |
| Expression Templates | Lazy evaluation | Eliminates temps |
| Type Traits | Type introspection | Compile-time |
| Fold Expressions | Parameter pack ops | Optimal |
| Template Specialization | Type-specific impl | Zero overhead |

View File

@@ -0,0 +1,12 @@
---
name: requirements
description: Update the Dota Factory requirements document with new or changed requirements
argument-hint: <description of new requirement>
disable-model-invocation: true
---
Read `docs/requirements.md`, then help the user update the requirements with the following change:
$ARGUMENTS
Ask any clarifying questions if the request is ambiguous, or flag any conflicts with existing requirements before making changes. Do not make changes to the requirements file before the answers are clear.