diff --git a/CMakeLists.txt b/CMakeLists.txt index 9c2c6e6..5b31a41 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -7,7 +7,7 @@ endif() # ========== Project ========== project(patternia - VERSION 0.9.4 + VERSION 0.9.5 DESCRIPTION "Header-only pattern matching library for modern C++" LANGUAGES CXX ) diff --git a/README.md b/README.md index c175a97..88015ae 100644 --- a/README.md +++ b/README.md @@ -174,7 +174,7 @@ target_link_libraries(your_target PRIVATE patternia::patternia) include(FetchContent) FetchContent_Declare(patternia GIT_REPOSITORY https://github.com/sentomk/patternia.git - GIT_TAG v0.9.4 + GIT_TAG v0.9.5 ) FetchContent_MakeAvailable(patternia) diff --git a/docs/changelog/releases.md b/docs/changelog/releases.md index d61c3e1..0014345 100644 --- a/docs/changelog/releases.md +++ b/docs/changelog/releases.md @@ -29,6 +29,7 @@ reflect the current supported API surface. - [v0.8.5](v0.8.5.md) - March 10, 2026 ## 0.9.x +- [v0.9.5](v0.9.5.md) - October 2026 - [v0.9.4](v0.9.4.md) - August 2026 - [v0.9.3](v0.9.3.md) - May 2026 - [v0.9.2](v0.9.2.md) - April 10, 2026 diff --git a/docs/changelog/v0.9.5.md b/docs/changelog/v0.9.5.md new file mode 100644 index 0000000..f8c9083 --- /dev/null +++ b/docs/changelog/v0.9.5.md @@ -0,0 +1,185 @@ +# Patternia v0.9.5 Release Note + +**Release Date:** October 2026 +**Version:** 0.9.5 + +--- + +## Overview + +Patternia v0.9.5 is a naming-and-dispatch release. `has<...>` is now +tuple-like, so a C++17 structured binding names the members of a +structural pattern without a macro, and `PTN_BIND` is deprecated in +its favor. Handler classification is rewritten so generic lambdas, +pointers to members and reference-returning handlers work, and the bare +spellings `is` and `alt` reach the static variant dispatch fast +path that only `is()` and `alt()` used to get. + +There are no breaking API changes. One behavior changed for code that +passed a pointer to member as a handler; see +[Behavior Changes](#behavior-changes). + +--- + +## New Features + +### Structured-binding member names + +`has<...>` implements the tuple-like protocol (`std::tuple_size`, +`std::tuple_element` and an ADL `get`). A structured binding declares +one member-anchored placeholder per non-ignored member: + +```cpp +struct Point { int x; int y; }; + +constexpr auto pt = has<&Point::x, &Point::y>; +auto [x, y] = pt; + +match(p) | on( + $(pt)[x * x + y * y == 25] >> "on circle", + _ >> "elsewhere" +); +``` + +Names follow the members listed in `has<...>`, and `_ign` slots do not +get a name: + +```cpp +constexpr auto pkt = has<&Pkt::type, &Pkt::len, ptn::_ign, &Pkt::crc>; +auto [type, len, crc] = pkt; // three names, one per member +``` + +The names are placeholders, not values extracted from a subject; +`$(pt)` still decides which values reach the handler. + +Scope rules: + +- Function-local `auto [x, y] = pt;` works with `on(...)`, but not with + `PTN_ON(...)`, whose captureless lambda cannot see automatic + bindings. +- For `PTN_ON`, declare the pattern and its names at namespace scope. +- In C++20, `static constexpr auto pt = ...;` plus + `static auto [x, y] = pt;` also works inside a function. +- In C++17, keep `PTN_BIND` for function-local names used inside + `PTN_ON`. + +Misuse is reported once, not as a cascade: a placeholder that does not +belong to the pattern, an out-of-range index into `has<...>`, or member +names on a non-structural pattern each produce a single diagnostic. +Guard diagnostics no longer mention `PTN_BIND`. + +### Handler forms + +Handler classification no longer instantiates the handler to find out +what it is. This fixes three cases that did not work before: + +- A generic lambda that reads members of its argument now compiles + inside `on(...)`: + + ```cpp + match(v) | on( + $(is()) >> [](const auto &c) { return c.r; }, + $(is()) >> [](const auto &s) { return s.side; }, + _ >> 0 + ); + ``` + +- Handlers that return a reference (a lambda returning `const T &`, or + a pointer to a data member such as `$ >> &Point::y`) are accepted. +- Pointers to members are handlers (see below). + +--- + +## Behavior Changes + +### Pointers to members are handlers + +`$(is()) >> &Circle::area` now calls `area` on the matched +value. Before, the pointer itself was the result of the case. Code that +relied on a case returning a member pointer has to wrap it in a lambda: + +```cpp +// v0.9.4: the case produced the member pointer +// v0.9.5: the case produces the call result +$(is()) >> &Circle::area + +// to keep the old meaning +$(is()) >> [] { return &Circle::area; } +``` + +--- + +## Deprecations + +### `PTN_BIND` + +`PTN_BIND` is deprecated in favor of structured-binding member names. +It keeps working, and it remains the way to name placeholders inside a +function that uses `PTN_ON` in C++17. The removal version is not +decided. + +```cpp +// v0.9.4 +PTN_BIND(Point, x, y); +$(has<&Point::x, &Point::y>)[x * x + y * y == 25] + +// v0.9.5 +constexpr auto pt = has<&Point::x, &Point::y>; +auto [x, y] = pt; +$(pt)[x * x + y * y == 25] +``` + +--- + +## Performance + +Bare `is` and `alt` are now stored as the canonical pattern types +that the dispatch optimizer recognizes. On an 8-alternative +`std::variant`, `is >> ...` and `alt >> ...` cases dispatch about +40% faster than in v0.9.4 and match the `is()` / `alt()` forms. +See the [v0.9.5 performance note](../performance/v0.9.5.md). + +--- + +## Bug Fixes + +- `-Wextra -Werror` users no longer fail on `#include `: + `negation_pattern::bind` named an argument it never used. +- Two unsequenced double-forwards in guard and tuple evaluation, and an + unconstrained forwarding constructor, found by the new clang-tidy + gate, are fixed. + +--- + +## Internal Improvements + +- CI: clang-tidy gate on headers, tests and samples; a libFuzzer + oracle target (`fuzz/ptn_fuzz_match`) with a smoke run on every PR and + a weekly deep run; pinned developer tools and CI dependency versions; + Linux matrix runners pinned to `ubuntu-24.04`. +- Bench: the PR benchmark job compares base and PR on one runner and + posts a sticky comment with the noise floor applied; generated charts + moved off `main` to the `bench-charts` branch. +- Tests: 256 tests (up from 204 in v0.9.4), including compile-fail + cases that pin the diagnostics for invalid member names and out-of-range + `has<...>` indices. +- Docs: tutorials and the API reference use structured-binding names. + +--- + +## Migration Guide + +Nothing needs to change to upgrade. To move off the deprecated macro: + +```cpp +// before +PTN_BIND(Point, x, y); +auto r = match(p) | on($(has<&Point::x, &Point::y>)[x < y] >> 1, _ >> 0); + +// after: namespace scope, also valid inside PTN_ON +constexpr auto pt = has<&Point::x, &Point::y>; +auto [x, y] = pt; +auto r = match(p) | on($(pt)[x < y] >> 1, _ >> 0); +``` + +Inside a function that uses `PTN_ON` in C++17, keep `PTN_BIND`. diff --git a/docs/performance/index.md b/docs/performance/index.md index 2b8c047..3277877 100644 --- a/docs/performance/index.md +++ b/docs/performance/index.md @@ -6,19 +6,25 @@ This section tracks performance-oriented algorithm evolution by version. ## Current Version -**v0.9.3** tuned the variant inline dispatch threshold for common workloads. +**v0.9.5** lets the bare spellings `is` and `alt` reach the static variant +dispatch fast path. +See [v0.9.5 performance note](v0.9.5.md) for details. + +v0.9.3 tuned the variant inline dispatch threshold for common workloads. See [v0.9.3 performance note](v0.9.3.md) for details. v0.8.2 established the lowering engine baseline. See [v0.8.2 performance note](v0.8.2.md) for benchmark data and algorithm details. -Releases from v0.8.3 to v0.9.2 focused on guard fixes, API ergonomics, and -compiler compatibility. No new dispatch algorithms were introduced. +Releases from v0.8.3 to v0.9.2, and v0.9.4, focused on guard fixes, API +ergonomics, and compiler compatibility. No new dispatch algorithms were +introduced. --- ## Version Index +- [v0.9.5](v0.9.5.md) (bare `is` / `alt` reach the variant fast path) - [v0.9.3](v0.9.3.md) (variant dispatch threshold tuning) - [v0.8.2](v0.8.2.md) (lowering engine baseline) - [v0.8.0](v0.8.0.md) (tiered variant dispatch) diff --git a/docs/performance/v0.9.5.md b/docs/performance/v0.9.5.md new file mode 100644 index 0000000..92e75a8 --- /dev/null +++ b/docs/performance/v0.9.5.md @@ -0,0 +1,73 @@ +# Patternia Performance - v0.9.5 + +**Published**: October 2026 + +**Focus**: Letting the bare spellings `is` and `alt` reach the static variant dispatch fast path. + +## Overview + +v0.9.5 stores the bare spellings `is` and `alt` as the same canonical +pattern types as `is()` and `alt()`. The dispatch optimizer and the +diagnostics only recognize the canonical types, so before this change a case +written as `is >> handler` fell back to sequential evaluation while +`is() >> handler` was lowered. No new dispatch algorithm was introduced and +no API changed; the two spellings now produce the same code. + +## Motivation + +`is` and `alt` are variable templates whose values are the factory +objects `is_factory` and `alt_factory`. The factories derive from +`type_is_pattern` and `type_alt_pattern`, but the optimizer matches on the +exact canonical types, not on types derived from them. The bare spelling is +the shorter and more common one in user code, so the common spelling missed +the fast path that the benchmarks, which use the call form, always measured. + +## Change + +| Component | Before (v0.9.4) | After (v0.9.5) | +|---|---|---| +| Pattern stored for `is` | `is_factory` | `type_is_pattern` | +| Pattern stored for `alt` | `alt_factory` | `type_alt_pattern` | + +`canonical_pattern_t` maps the two factories to the canonical types, and +`operator>>` and `$(...)` apply it when they store a pattern. The factories +carry no state, so converting them loses nothing and behavior is unchanged. +The change lives in: + +- [pattern_base.hpp](../../include/ptn/pattern/base/pattern_base.hpp) +- [ops.hpp](../../include/ptn/core/dsl/ops.hpp) + +## Benchmarks + +Setup: an 8-alternative `std::variant` of empty-ish structs, 4096 values in a +pseudo-random but fixed order, one `match(v) | PTN_ON(...)` per value with a +`_` fallback. Each number is the median of 15 repetitions of 200 passes over +the data; the table gives the median of three process runs. Apple M4 Pro, +Apple clang 17.0.0, `-std=c++17 -O2 -DNDEBUG`, headers from the `v0.9.4` tag +and from this release. + +| Case | v0.9.4 (ns/op) | v0.9.5 (ns/op) | Delta | +|---|---|---|---| +| `is >> ...` (bare) | 2.18 | 1.32 | -39.7% | +| `alt >> ...` (bare) | 2.10 | 1.27 | -39.4% | +| `is() >> ...` (call) | 1.31 | 1.29 | -2.1% (noise) | +| `alt() >> ...` (call) | 1.28 | 1.35 | +5.1% (noise) | +| `std::visit` | 3.64 | 3.70 | +1.4% (noise) | + +The call forms and `std::visit` did not change, which is what the change +predicts: they were already on the fast path, and `std::visit` does not use the +library at all. After the change all four Patternia spellings sit within run +noise of each other. The first process run was noticeably slower on both +versions (for example 2.94 vs 1.83 ns/op for bare `is`), so only the +relative result between the two versions in the same run is meaningful, and +the absolute numbers depend on the machine. + +These are single-machine numbers from one compiler. GCC and MSVC were not +measured; the CI benchmark comparison on the pull request is the cross-check. + +## Outcome + +v0.9.5 is a small performance release. Code written with the bare `is` and +`alt` spellings gets the same variant dispatch as the call forms, about 40% +faster on an 8-alternative variant in this measurement, and nothing else in the +dispatch strategy changes. diff --git a/docs/roadmap.md b/docs/roadmap.md index d913869..69a1d47 100644 --- a/docs/roadmap.md +++ b/docs/roadmap.md @@ -11,7 +11,7 @@ Status labels: ## FINISHED -Completed on `main` (unreleased): +Completed in [v0.9.5](changelog/v0.9.5.md): - `has<...>` is tuple-like: `auto [x, y] = has<&Point::x, &Point::y>;` declares member-anchored guard placeholders without a macro. @@ -19,6 +19,8 @@ Completed on `main` (unreleased): It remains supported, and is still needed for function-local names inside `PTN_ON` in C++17. - Bare `is` and `alt` reach the static variant dispatch fast path. +- Handler classification no longer instantiates generic lambdas; pointers + to members and reference-returning handlers are accepted. Completed in [v0.9.4](changelog/v0.9.4.md): diff --git a/mkdocs.yml b/mkdocs.yml index 47e4c03..2656892 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -92,12 +92,14 @@ nav: - Performance: - Overview: performance/index.md + - v0.9.5: performance/v0.9.5.md - v0.9.3: performance/v0.9.3.md - v0.8.2: performance/v0.8.2.md - v0.8.0: performance/v0.8.0.md - Changelog: - Releases: changelog/releases.md + - v0.9.5: changelog/v0.9.5.md - v0.9.4: changelog/v0.9.4.md - v0.9.3: changelog/v0.9.3.md - v0.9.2: changelog/v0.9.2.md diff --git a/packaging/vcpkg/vcpkg.json b/packaging/vcpkg/vcpkg.json index df8313e..2fd32f2 100644 --- a/packaging/vcpkg/vcpkg.json +++ b/packaging/vcpkg/vcpkg.json @@ -1,6 +1,6 @@ { "name": "patternia", - "version": "0.9.4", + "version": "0.9.5", "description": "Header-only pattern matching library for modern C++", "homepage": "https://github.com/SentoMK/patternia", "license": "MIT",