A header-only, zero-overhead pattern matching library for modern C++ that transforms branching logic into expressive, maintainable code.
- Release update (v0.8.2) keeps public usage centered on
match(x) | on(...)and adds reusable hot-path forms throughstatic_on(...)andPTN_ON(...). - Performance update (v0.8.2) introduces a lowering engine with
full/bucketed/nonelegality and a switch-oriented static literal path for large keyed matches. - Literal API update (v0.8.2) keeps
lit(value)andlit_ci(value)for general runtime matching while usinglit<value>()for lowering-friendly compile-time literals.
Current snapshots are generated from benchmark JSON via
scripts/bench_single_report.py.
For the full algorithm discussion behind v0.8.2, see the Performance Notes.
The 128-way literal benchmark exists because a small literal chain does not show the real problem.
Patternia needed one benchmark large enough to answer two separate questions:
- how close
lit<...>()lowering is to a hand-writtenswitch - how much extra cost comes from rebuilding
on(...)on every call
This chart isolates the total cost of the plain pipeline form:
match(x) | on(...) is expressive, but in a hot loop it still rebuilds:
- case objects
- value handlers
- the final
on(...)tuple
That is why this comparison does not only measure dispatch quality. It also measures matcher construction overhead.
This chart isolates the forms that avoid rebuilding the matcher:
The two cached forms answer slightly different needs.
Advantages:
- lowest-overhead reusable form in user code
- easy for performance-sensitive loops and routing tables
- makes matcher lifetime explicit
Tradeoffs:
- more ceremony than the pipeline form
- not ideal when you want the match site to stay compact
Good fit:
- hot dispatch loops
- local routing helpers
- benchmark baselines
static auto cases = on(
lit<1>() >> 1,
lit<2>() >> 2,
__ >> 0
);
return match(x) | cases;Advantages:
- keeps the
match(x) | ...pipeline syntax - avoids repeated matcher construction
- much easier to drop into existing call sites than manual caching
Tradeoffs:
- still a macro-based entry point
- not as explicit as a named
static auto casesobject when you want to share the matcher across multiple functions or call sites
Good fit:
- pipeline-style code paths
- performance-sensitive matches that still want compact call-site syntax
- cases where you want most of the cached performance without introducing a named matcher object
return match(x) | PTN_ON(
lit<1>() >> 1,
lit<2>() >> 2,
__ >> 0
);Current variant snapshot is generated from ptn_bench_variant JSON:
ptn_bench_variant currently focuses on variant dispatch and registers these
scenario groups (all compared against StdVisit / Sequential / SwitchIndex
where applicable):
VariantMixed/VariantAltHotVariantFastPathMixed/VariantFastPathAltHotVariantAltIndexMixed/VariantAltIndexAltHotVariantAltIndex32Mixed/VariantAltIndex32AltHot
Use: baseline std::variant<int, std::string> routing with two different
input distributions.
Code shape:
match(v) | on(
type::is<int>() >> 1,
type::is<std::string>() >> 2,
__ >> 0
);Meaning:
VariantMixed: mixed alternatives, reflects average dispatch behavior.VariantAltHot: one alternative is hot, shows branch-locality sensitivity.- Used as a baseline for
type::is<T>()paths.
Use: verify the simple variant fast path (minimal pattern complexity).
Code shape:
match(v) | on(
type::alt<0>() >> 1,
type::alt<1>() >> 2,
__ >> 0
);Meaning:
- Isolates dispatch overhead when patterns are direct alt checks.
- Confirms whether optimized simple-dispatch entry is effective.
Use: same 2-alt routing but expressed explicitly through type::alt<I>().
Code shape:
match(v) | on(
type::alt<0>() >> 1,
type::alt<1>() >> 2,
__ >> 0
);Meaning:
- Measures indexed dispatch behavior independent of type-name matching.
- Helps distinguish type-based vs index-based dispatch costs.
Use: stress test for large branch sets (32 alternatives).
Code shape:
match(v32) | on(
type::alt<0>() >> 1,
type::alt<1>() >> 2,
// ...
type::alt<31>() >> 32,
__ >> 0
);Meaning:
- Directly targets large-N dispatch scalability.
- Evaluates effectiveness of compact map + cold-path strategy.
- Compared against
SwitchIndexbaseline for dense branch routing.
- Quick Start
Install Patternia and write your first pattern match case.
- From Control Flow to Pattern Matching
Refactoringif/switchinto declarative pattern-based logic.
- Pattern Matching in Other Languages
How Patternia relates to pattern matching systems in other languages.
- Custom Predicate Guards
Defining and using custom guards inwhen(...).
-
Policy Constraint Matching
Expressing rule-based access policies as declarative constraints. -
Geometric Constraint Matching
Expressingx² + y² + z² < 1as a declarative pattern.
Pattern matching is a control-flow mechanism that selects execution paths based on the structural form, construction, or type of values. By combining discrimination, decomposition, and structured binding into a single construct, pattern matching allows programs to reason about data shapes directly, rather than through ad-hoc conditional logic. Compared to traditional if-else chains or switch statements, pattern matching offers a more declarative and data-oriented way to express branching logic.
Further reading:
- Haskell Pattern Matching
- Rust Pattern Matching
- Scala Match Expressions
- Python Structural Pattern Matching (PEP 634)
In modern C++, control flow over heterogeneous or structured data is typically expressed using a combination of if/else, switch, type checks, and manual data access. While these mechanisms are flexible and expressive, they tend to scale poorly as data structures become more complex or evolve over time. Type discrimination, structural access, and business logic are often interleaved, making the resulting control flow harder to read, maintain, and extend.
// Conventional control flow (conceptual)
if (value is TypeA) {
auto& a = as<TypeA>(value);
if (a.field > threshold) {
...
}
} else if (value is TypeB) {
auto& b = as<TypeB>(value);
...
} else {
...
}In this style, control flow is organized around conditions and checks, rather than around the structure of the data itself. The logic for discriminating types, accessing fields, and introducing local variables is scattered across branches, often leading to duplicated checks and implicit assumptions about data invariants.
With pattern matching, control flow can instead be organized around data shapes. Each branch explicitly states the structure it expects and introduces bindings only when the match succeeds. This aligns control flow more closely with the semantics of the data and makes each branch self-contained.
// Control flow with pattern matching (conceptual)
match value {
when PatternA(x) if x > threshold => {
...
}
when PatternB(y) => {
...
}
otherwise => {
...
}
}By unifying discrimination, decomposition, and binding, pattern matching allows control flow to be expressed declaratively and locally. This approach reduces boilerplate, minimizes accidental complexity, and provides a clearer foundation for reasoning about completeness and correctness as data structures evolve.
Patternia is designed to address several long-standing pain points in C++ control flow and data-oriented logic—especially in codebases that operate on evolving data structures, heterogeneous types, or complex branching rules.
In idiomatic C++, logic that depends on the shape or internal structure of data is typically expressed through a mix of:
- type checks (
std::variant,dynamic_cast, tag fields), - manual field access,
- nested conditionals,
- and ad-hoc invariants enforced implicitly by control flow.
As a result, structural assumptions about data are rarely explicit, and reasoning about correctness often requires reading across multiple branches.
Patternia allows control flow to be written in terms of structure:
match(p) | on(
bind(has<&Point::x, &Point::y>()) >> [](int x, int y) {
// explicitly operates on {x, y}
},
__ >> [] { /* fallback */ }
);Each branch clearly states what shape of data it expects and what it binds, making invariants explicit and local.
In conventional C++, decomposition (extracting fields) and conditional checks are usually interleaved:
if (p.x + p.y == 0 && p.x > 0) {
...
}As conditions grow more complex—especially when they involve relationships between multiple values—this style becomes increasingly opaque.
Patternia separates these concerns:
- Patterns describe how data is decomposed.
- Guards describe constraints over the bound values.
match(p) | on(
bind(has<&Point::x, &Point::y>())[arg<0> + arg<1> == 0] >>
[](int x, int y) { /* ... */ },
__ >> [] { /* ... */ }
);This separation improves readability and enables richer forms of reasoning over multi-value relationships.
Traditional control flow relies on raw boolean expressions, which:
- do not compose well,
- cannot be reused independently of control flow,
- and provide no structural context.
Patternia introduces first-class guard expressions that are:
- composable (
&&,||), - expressive (relational, arithmetic, predicate-based),
- and structurally aware (single-value and multi-value guards).
bind()[_ > 0 && _ < 10]
bind(has<&A::x, &A::y>())[arg<0> * arg<1> > 100]Guards become part of the pattern language rather than incidental conditions.
C++ provides multiple mechanisms for branching:
switchfor integral values,if constexprfor compile-time decisions,std::visitfor variants,- and manual destructuring for aggregates.
These mechanisms are orthogonal and non-uniform, often forcing developers to mix paradigms within the same function.
Patternia offers a single abstraction that can express:
- literal matching,
- relational matching,
- structural decomposition,
- guarded constraints,
- and fallback behavior
within one coherent, expression-oriented model.
Patternia treats pattern matching as an expression, not just a control-flow statement:
auto result = match(n) | on(
lit(0) >> 0,
lit(1) >> 1,
__ >> [] { return compute(); }
);At the same time, it adheres strictly to C++’s zero-overhead principle:
- no runtime type erasure,
- no virtual dispatch,
- no heap allocation,
- no hidden control flow.
All matching logic is resolved through templates and inlined calls, allowing the compiler to optimize aggressively.
For literal patterns, Patternia keeps two distinct entry points:
- lit(value) and lit_ci(value) store a runtime value and are the default choice for general matching, dynamic values, and strings.
- lit() encodes the literal in the type and is intended for compile-time-known integral and enum cases where the lowering engine can consider switch-like optimizations.
Ultimately, Patternia is not about replacing if or switch.
It is about elevating data shape—structure, relationships, and constraints—to a first-class concept in C++ control flow.
This makes Patternia particularly suitable for:
- state machines,
- protocol handling,
- geometric and numeric logic,
- AST processing,
- rule-based systems,
- and any domain where what the data looks like matters as much as what its value is.
Patternia is a header-only library that brings expressive pattern matching to modern C++.
Key Features:
- Zero-overhead abstraction with compile-time optimization
- Expression-oriented matching with value-returning capabilities
- Structural pattern matching for complex data shapes
- Composable guard system for conditional logic
- Type-safe bindings and explicit data flow
Quick Example:
#include <ptn/patternia.hpp>
int classify(int x) {
using namespace ptn;
return match(x) | on(
lit(0) >> 0,
lit(1) >> 1,
__ >> -1
);
}For complete installation instructions, comprehensive examples, and in-depth tutorials, visit the Getting Started Guide.
cmake -S . -B build -DPTN_BUILD_TESTS=ON
cmake --build build --target ptn_tests./build/tests/ptn_testsOn Windows (PowerShell):
.\build\tests\ptn_tests.exeRuntime tests (ptn_tests):
LiteralPattern.LitMatchesExpectedValue: verifieslit(...)exact-match branch selection.LiteralPattern.LitOtherwiseFallback: verifies fallback when no literal case matches.LiteralPattern.LitCiMatchesAsciiCaseInsensitive: verifieslit_ci(...)ASCII case-insensitive matching.MatchFlow.OtherwiseCallableWithSubject: verifies.otherwise(...)callable can receive subject.MatchFlow.WildcardEndFlow: verifies wildcard__+.end()terminal flow.MatchFlow.SubjectBindsAsLvalue: regression check that subject binding stays on lvalue path.TypePattern.TypeIsAndTypeAs: verifiestype::is<T>()andtype::as<T>()dispatch forstd::variant.TypePattern.AltByIndex: verifiesalt<I>()index-based variant dispatch.Guard.UnaryPlaceholderPredicate: verifies unary guard expressions with_.Guard.RangeHelperModes: verifies range helper behavior (rng) across interval modes.Guard.MultiArgExpressionPredicate: verifies tuple guard expressions usingarg<N>.Guard.MultiArgCallablePredicate: verifies callable multi-argument guard predicates.TerminalSemantics.OtherwiseUsedWhenNoCaseMatches: verifies fallback executes when all cases fail.TerminalSemantics.OtherwiseNotInvokedOnMatch: verifies fallback is skipped on matched branch.TerminalSemantics.EndWithWildcardReturnsFallbackCase: verifies wildcard fallback result under.end().TerminalSemantics.FirstMatchingCaseWins: verifies first-match-wins evaluation order.
Compile-fail tests (ctest -R compile_fail):
compile_fail.otherwise_with_wildcard: rejects.otherwise(...)when wildcard__is already present.compile_fail.end_without_wildcard: rejects.end()without wildcard fallback.compile_fail.case_after_wildcard: rejects adding.when(...)after wildcard case.compile_fail.cases_with_binding: rejects binding patterns insidecases(...).compile_fail.cases_with_guard: rejects guard-attached binding patterns insidecases(...).
These tests are expected to fail compilation, and pass only when the failure is correctly detected.
ctest --test-dir build -R compile_fail --output-on-failurectest --test-dir build --output-on-failurev0.8.0 keeps the unified benchmark suite and adds tiered-variant dispatch
optimizations. For dispatch-focused evaluation, prefer ptn_bench_variant.
- Unit: nanoseconds (
ns) - Stable profile: min time + repetitions + aggregate output
- Filter:
Variant
# 0) Build variant benchmark target
cmake --build build --config Release --target ptn_bench_variant --parallel
# 1) Run variant-focused suite
.\build\bench\ptn_bench_variant.exe `
--benchmark_filter="Variant" `
--benchmark_min_time=0.5 `
--benchmark_repetitions=20 `
--benchmark_report_aggregates_only=true `
--benchmark_out="build/bench/variant_current.json" `
--benchmark_out_format=json
# 2) Single-file visualization (same JSON, multi-implementation)
py -3 scripts/bench_single_report.py `
--input "build/bench/variant_current.json" `
--include "Variant" `
--outdir "build/bench/single" `
--prefix "variant_current"
# 3) Optional: compare two snapshots
py -3 scripts/bench_stage_results.py `
--baseline "build/bench/variant_baseline.json" `
--current "build/bench/variant_current.json"
py -3 scripts/bench_compare.py `
--include "Variant" `
--label-baseline "baseline" `
--label-current "current" `
--prefix "variant_compare"Jump directly to a specific part of the Patternia API.
match(subject)match(subject) | on(...).when(pattern >> handler)(legacy chain; deprecating).otherwise(...)(legacy chain; deprecating).end()(legacy chain; deprecating)- Fallback Mechanisms Comparison
[]Guard Attachment_Placeholder (Single-value Guards)rng(...)Range Guardsarg<N>(Multi-value Guards)- Custom Predicate Guards (Lambda)
has<&T::member...>- Structural Constraints
- Structural Binding with
bind() - Partial Structural Matching
- Design Rationale & Guarantees
Note
This API reference documents Patternia’s language constructs, not just function signatures. Each section explains the semantic role and design intent of the API, in addition to usage examples.
Contributions are welcome.
Please read CONTRIBUTING.md before submitting issues or pull requests.
This project is governed by a Code of Conduct.


