move claude project files into git repo
This commit is contained in:
148
.claude/CLAUDE.md
Normal file
148
.claude/CLAUDE.md
Normal 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.
|
||||||
28
.claude/skills/bug/SKILL.md
Normal file
28
.claude/skills/bug/SKILL.md
Normal 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.
|
||||||
91
.claude/skills/cpp-pro/SKILL.md
Normal file
91
.claude/skills/cpp-pro/SKILL.md
Normal 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
|
||||||
443
.claude/skills/cpp-pro/references/build-tooling.md
Normal file
443
.claude/skills/cpp-pro/references/build-tooling.md
Normal 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` |
|
||||||
440
.claude/skills/cpp-pro/references/concurrency.md
Normal file
440
.claude/skills/cpp-pro/references/concurrency.md
Normal 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 |
|
||||||
400
.claude/skills/cpp-pro/references/memory-performance.md
Normal file
400
.claude/skills/cpp-pro/references/memory-performance.md
Normal 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 |
|
||||||
307
.claude/skills/cpp-pro/references/modern-cpp.md
Normal file
307
.claude/skills/cpp-pro/references/modern-cpp.md
Normal 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 | - | - | ✓ |
|
||||||
360
.claude/skills/cpp-pro/references/templates.md
Normal file
360
.claude/skills/cpp-pro/references/templates.md
Normal 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 |
|
||||||
12
.claude/skills/requirements/SKILL.md
Normal file
12
.claude/skills/requirements/SKILL.md
Normal 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.
|
||||||
4
.gitignore
vendored
4
.gitignore
vendored
@@ -1 +1,5 @@
|
|||||||
/build/
|
/build/
|
||||||
|
|
||||||
|
# local Claude Code config (machine-specific; .mcp.json holds credentials)
|
||||||
|
/.mcp.json
|
||||||
|
/.claude/settings.local.json
|
||||||
|
|||||||
Reference in New Issue
Block a user