Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
)
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down
1 change: 1 addition & 0 deletions docs/changelog/releases.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
185 changes: 185 additions & 0 deletions docs/changelog/v0.9.5.md
Original file line number Diff line number Diff line change
@@ -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<T>` and `alt<I>` reach the static variant dispatch fast
path that only `is<T>()` and `alt<I>()` 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<Circle>()) >> [](const auto &c) { return c.r; },
$(is<Square>()) >> [](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>()) >> &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>()) >> &Circle::area

// to keep the old meaning
$(is<Circle>()) >> [] { 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<T>` and `alt<I>` are now stored as the canonical pattern types
that the dispatch optimizer recognizes. On an 8-alternative
`std::variant`, `is<T> >> ...` and `alt<I> >> ...` cases dispatch about
40% faster than in v0.9.4 and match the `is<T>()` / `alt<I>()` forms.
See the [v0.9.5 performance note](../performance/v0.9.5.md).

---

## Bug Fixes

- `-Wextra -Werror` users no longer fail on `#include <ptn/patternia.hpp>`:
`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`.
12 changes: 9 additions & 3 deletions docs/performance/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -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<T>` and `alt<I>` 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<T>` / `alt<I>` 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)
Expand Down
73 changes: 73 additions & 0 deletions docs/performance/v0.9.5.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
# Patternia Performance - v0.9.5

**Published**: October 2026

**Focus**: Letting the bare spellings `is<T>` and `alt<I>` reach the static variant dispatch fast path.

## Overview

v0.9.5 stores the bare spellings `is<T>` and `alt<I>` as the same canonical
pattern types as `is<T>()` and `alt<I>()`. The dispatch optimizer and the
diagnostics only recognize the canonical types, so before this change a case
written as `is<T> >> handler` fell back to sequential evaluation while
`is<T>() >> handler` was lowered. No new dispatch algorithm was introduced and
no API changed; the two spellings now produce the same code.

## Motivation

`is<T>` and `alt<I>` are variable templates whose values are the factory
objects `is_factory<T>` and `alt_factory<I>`. 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<T>` | `is_factory<T>` | `type_is_pattern<T>` |
| Pattern stored for `alt<I>` | `alt_factory<I>` | `type_alt_pattern<I>` |

`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<T> >> ...` (bare) | 2.18 | 1.32 | -39.7% |
| `alt<I> >> ...` (bare) | 2.10 | 1.27 | -39.4% |
| `is<T>() >> ...` (call) | 1.31 | 1.29 | -2.1% (noise) |
| `alt<I>() >> ...` (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<T>`), 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<T>` and
`alt<I>` 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.
4 changes: 3 additions & 1 deletion docs/roadmap.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,16 @@ 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.
- `PTN_BIND` is deprecated in favor of structured-binding member names.
It remains supported, and is still needed for function-local names
inside `PTN_ON` in C++17.
- Bare `is<T>` and `alt<I>` 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):

Expand Down
2 changes: 2 additions & 0 deletions mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion packaging/vcpkg/vcpkg.json
Original file line number Diff line number Diff line change
@@ -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",
Expand Down
Loading