A header-only, zero-overhead pattern matching library for modern C++ that transforms branching logic into expressive, maintainable code.
- Release update (v0.8.0) — removes
match(x, cases(...)).end()and focuses public usage onmatch(x) | on{...}. - Performance update (v0.8.0) — adds tiered variant dispatch (
hot_inline/warm_segmented/cold_compact) and large-branch cold-path separation. - Variant matching support (v0.7.x+) — includes
type::is,type::as, andtype::alt.
Current snapshot is generated from ptn_bench_variant JSON via
scripts/bench_single_report.py (same JSON, multi-implementation comparison):
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.
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.
