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.
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:
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:
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 withon(...), but not withPTN_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 = ...;plusstatic auto [x, y] = pt;also works inside a function. - In C++17, keep
PTN_BINDfor function-local names used insidePTN_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(...):
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:
// 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.
// 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.
Bug Fixes¶
-Wextra -Werrorusers no longer fail on#include <ptn/patternia.hpp>:negation_pattern::bindnamed 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 toubuntu-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
mainto thebench-chartsbranch. - 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:
// 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.