Skip to content

sentomk/patternia

Patternia logo

C++17+ Build License Version Docs


A header-only, zero-overhead pattern matching library for modern C++ that transforms branching logic into expressive, maintainable code.

Update

  • Release update (v0.8.2) keeps public usage centered on match(x) | on(...) and adds reusable hot-path forms through static_on(...) and PTN_ON(...).
  • Performance update (v0.8.2) introduces a lowering engine with full / bucketed / none legality and a switch-oriented static literal path for large keyed matches.
  • Literal API update (v0.8.2) keeps lit(value) and lit_ci(value) for general runtime matching while using lit<value>() for lowering-friendly compile-time literals.

Performance Snapshot

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.

Literal Bench Guide (ptn_bench_lit)

Why the 128-way benchmark exists

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-written switch
  • how much extra cost comes from rebuilding on(...) on every call

1) Raw inline on(...) vs switch

This chart isolates the total cost of the plain pipeline form:

Patternia Literal Match 128 Raw On vs Switch (v0.8.2)

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.

2) Cached matcher forms vs switch

This chart isolates the forms that avoid rebuilding the matcher:

Patternia Literal Match 128 Cached Matcher vs Switch (v0.8.2)

The two cached forms answer slightly different needs.

static auto cases = on(...);

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;
PTN_ON(...)

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 cases object 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
);

Variant Snapshot

Current variant snapshot is generated from ptn_bench_variant JSON:

Patternia Variant Dispatch Snapshot (v0.8.0)

Variant Bench Guide (ptn_bench_variant)

ptn_bench_variant currently focuses on variant dispatch and registers these scenario groups (all compared against StdVisit / Sequential / SwitchIndex where applicable):

  • VariantMixed / VariantAltHot
  • VariantFastPathMixed / VariantFastPathAltHot
  • VariantAltIndexMixed / VariantAltIndexAltHot
  • VariantAltIndex32Mixed / VariantAltIndex32AltHot

1) VariantMixed / VariantAltHot (baseline type dispatch)

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.

2) VariantFastPathMixed / VariantFastPathAltHot (simple-case fast path)

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.

3) VariantAltIndexMixed / VariantAltIndexAltHot (index-addressed dispatch)

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.

4) VariantAltIndex32Mixed / VariantAltIndex32AltHot (32-branch scale test)

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 SwitchIndex baseline for dense branch routing.

Learn Patternia

Getting Started

  • Quick Start
    Install Patternia and write your first pattern match case.

Tutorials

Getting Into Pattern Matching

For Readers Familiar with Pattern Matching

Core Techniques

Worked Examples

What Is Pattern Matching

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:

Control Flow in C++

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.

What Patternia Solves

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.

1. Scattered Control Logic over Structured Data

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.


2. Interleaving Decomposition and Conditional Logic

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.


3. Lack of Expressive, Composable Guards

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.


4. Unifying Value-Based and Structural Branching

C++ provides multiple mechanisms for branching:

  • switch for integral values,
  • if constexpr for compile-time decisions,
  • std::visit for 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.


5. Expression-Oriented Matching with Zero Overhead

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.

6. Making Data Shape a First-Class Concept

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.

Quick Start

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.

Testing

Build Tests

cmake -S . -B build -DPTN_BUILD_TESTS=ON
cmake --build build --target ptn_tests

Run Runtime Tests (GoogleTest)

./build/tests/ptn_tests

On Windows (PowerShell):

.\build\tests\ptn_tests.exe

Test Case Coverage

Runtime tests (ptn_tests):

  • LiteralPattern.LitMatchesExpectedValue: verifies lit(...) exact-match branch selection.
  • LiteralPattern.LitOtherwiseFallback: verifies fallback when no literal case matches.
  • LiteralPattern.LitCiMatchesAsciiCaseInsensitive: verifies lit_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: verifies type::is<T>() and type::as<T>() dispatch for std::variant.
  • TypePattern.AltByIndex: verifies alt<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 using arg<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 inside cases(...).
  • compile_fail.cases_with_guard: rejects guard-attached binding patterns inside cases(...).

Run Compile-Fail Tests (CTest)

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-failure

Run All Tests Through CTest

ctest --test-dir build --output-on-failure

Benchmarking (v0.8.0)

v0.8.0 keeps the unified benchmark suite and adds tiered-variant dispatch optimizations. For dispatch-focused evaluation, prefer ptn_bench_variant.

Recommended Variant Run Profile

  • Unit: nanoseconds (ns)
  • Stable profile: min time + repetitions + aggregate output
  • Filter: Variant

Reproducible Compare Workflow

# 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"

API Reference

Quick Navigation

Jump directly to a specific part of the Patternia API.


I. Match DSL Core Framework


II. Pattern Primitives


III. Guard System


IV. Structural Matching


V. Namespace Structure


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.

Contributing

Contributions are welcome.

Please read CONTRIBUTING.md before submitting issues or pull requests.
This project is governed by a Code of Conduct.


Built for modern C++ development

About

Providing pattern matching for modern c++.

Topics

Resources

License

Code of conduct

Contributing

Security policy

Stars

Watchers

Forks

Contributors