Skip to content

Getting Started

Patternia is a header-only library. Once installed or fetched into your project, you can start using pattern matching immediately.

Below is minimal working example demonstrating the core DSL:


To keep your chained .when() and .otherwise() expressions visually aligned and easy to scan, you can add a minimal .clang-format to your project root:

# patternia .clang-format (minimal)
BasedOnStyle: LLVM
IndentWidth: 2            # or 4
ContinuationIndentWidth: 4 # or 6
ColumnLimit: 0
BinPackArguments: false
BinPackParameters: false
BreakBeforeBinaryOperators: None

With this style, multi-line match expressions remain clean and consistent:

auto out =
    match(5)
      .when(lit(0) >> "zero")
      .when(lit(5) >> "five")
      .otherwise("other");

1. Installation

Patternia is a header-only library. No compilation or binary linking is required.

Patternia can be integrated directly via CMake FetchContent. This is the recommended approach during active development.

include(FetchContent)

FetchContent_Declare(
  patternia
  GIT_REPOSITORY https://github.com/SentoMK/patternia.git
  GIT_TAG        main
)

FetchContent_MakeAvailable(patternia)

Then, include Patternia headers in your code:

#include "ptn/patternia.hpp"

Notes:

  • GIT_TAG main tracks the latest development version
  • Patternia is header-only — no targets need to be linked
  • Requires C++17 or later

By vcpkg

vcpkg support is coming soon.

Other Installation Methods

For additional installation options and detailed setup instructions (including manual integration and future package manager support), see:

👉 Patternia Installation

2. Matching Values

Basic DSL Syntax

At its simplest, Patternia replaces if / switch with a declarative, ordered match expression.

#include <iostream>
#include <ptn/patternia.hpp>

using namespace ptn;

int main() {
  int x = 2;

  match(x)
    .when(lit(1) >> [] { std::cout << "one\n"; })
    .when(lit(2) >> [] { std::cout << "two\n"; })
    .otherwise([] { std::cout << "other\n"; });
}

Pattern Fallback with __ and .end()

For exhaustive data types where you want to use pattern fallback:

#include <iostream>
#include <ptn/patternia.hpp>

using namespace ptn;

int main() {
  int x = 2;

  match(x)
    .when(lit(1) >> [] { std::cout << "one\n"; })
    .when(lit(2) >> [] { std::cout << "two\n"; })
    .when(__ >> [] { std::cout << "other values\n"; })  // pattern fallback
    .end();  // required for __ to work
}

Tuple-Based Syntax (Simple Cases)

For simple matching scenarios, Patternia provides a concise tuple-based syntax:

#include <iostream>
#include <ptn/patternia.hpp>

using namespace ptn;

int main() {
  int x = 2;

  match(x, cases(
    lit(1) >> [] { std::cout << "one\n"; },
    lit(2) >> [] { std::cout << "two\n"; },
    __    >> [] { std::cout << "other\n"; }  // pattern fallback
  )).end();
}

Key points:

  • match(subject, cases(...)) provides a compact syntax for simple cases
  • No bindings/guards: This syntax cannot be used with bind() or guard expressions
  • Cases are tested top-to-bottom (first-match semantics)
  • Supports both pattern fallback (__) and match fallback patterns
  • Requires .end() to trigger evaluation when using __ pattern
  • Ideal for straightforward value matching without complex conditions

Limitations: - Cannot use bind() (and therefore cannot use guard expressions []) - Best suited for simple literal and wildcard matching - Must use .end() when using __ pattern to trigger evaluation

For complex matching with guards, use the standard DSL syntax shown above.

3. Fallback Mechanisms and Match Completion

Patternia supports value-returning matches, similar to Rust or Scala.

int classify(int x) {
  return match(x)
    .when(lit(0) >> 0)
    .when(lit(1) >> 1)
    .otherwise(-1);  // returns a value
}

Both .otherwise() and .end() can be used for either value-returning or side-effect scenarios. The key distinction is:

  • .otherwise(): Match fallback that works in all scenarios
  • .end(): Required when using __ pattern fallback

Using .otherwise():

auto result = match(value)
  .when(lit(1) >> 100)
  .when(lit(2) >> 200)
  .otherwise(0);  // match fallback

Using .end() with __:

match(value)
  .when(lit(1) >> [] { std::cout << "case 1\n"; })
  .when(lit(2) >> [] { std::cout << "case 2\n"; })
  .when(__ >> [] { std::cout << "fallback\n"; })  // pattern fallback
  .end();  // required for __

Key Point: .end() is specifically required when using the __ pattern, while .otherwise() can be used independently of __.

4. Binding Values

To access matched values inside a handler, introduce bindings explicitly. For ordinary subjects, use bind(). For std::variant alternatives, you can also use as<T>() as an explicit binding shorthand.

match(x)
  .when(bind() >> [](int v) {
    std::cout << "value = " << v << "\n";
  })
  .otherwise([] {});

Bindings are always explicit—nothing is bound implicitly. bind() is the primitive binding pattern, and as<T>() is a named shorthand for is<T>(bind()).

This makes data flow visible and predictable, especially in complex matches.

5. Guards (Conditional Matching)

Guards allow you to attach constraints to patterns.

using namespace ptn;

match(x)
  .when(bind()[_ > 0 && _ < 10] >> [](int v) {
    std::cout << "in range: " << v << "\n";
  })
  .otherwise([] {
    std::cout << "out of range\n";
  });
  • _ is a placeholder for the bound value
  • Guard expressions are composable and side-effect free
  • Guards run only after the pattern itself matches

6. Structural Matching

Patternia can match structure, not just values.

struct Point {
  int x;
  int y;
};

Point p{3, -3};

match(p)
  .when(
    bind(has<&Point::x, &Point::y>())[arg<0> + arg<1> == 0] >>
    [](int x, int y) {
      std::cout << "on diagonal\n";
    }
  )
  .otherwise([] {});

Here:

  • has<&Point::x, &Point::y> describes the expected shape
  • bind(...) extracts values explicitly
  • Guards can express relationships between multiple bindings

7. Variant Type Matching (std::variant)

Patternia can match std::variant alternatives by type using type::is<T>() or its simplified version is<T>(). If you want to bind the alternative value, use as<T>(), which is explicit binding sugar for is<T>(bind()).

using V = std::variant<int, std::string, Point>;

match(v)
  .when(is<int>() >> [] { /* type-only */ })
  .when(as<std::string>() >> [](const std::string &s) { /* bound */ })
  .when(as<std::string>()[_ != ""] >> [](const std::string &s) { /* guarded */ })
  .when(is<Point>(bind(has<&Point::x, &Point::y>())) >>
        [](int x, int y) { /* structural bind */ })
  .otherwise([] {});

8. Pattern Fallback with __ and .end()

Use __ as a pattern fallback that must be paired with .end() to trigger match inference:

// For exhaustive data types (integers, enums, etc.)
match(x)
  .when(lit(0) >> [] { std::cout << "zero\n"; })
  .when(lit(1) >> [] { std::cout << "one\n"; })
  .when(__ >> [] { std::cout << "other values\n"; })  // pattern fallback
  .end();  // required for __ to work

Important: __ (pattern fallback) must be used with .end() to enable match inference. Without .end(), the __ case will not trigger.

9. Match Fallback with .otherwise()

Use .otherwise() as a match fallback for scenarios where exhaustive matching is not possible or practical:

// For non-exhaustive or complex data types
match(data)
  .when(valid_pattern >> handler)
  .otherwise([] { std::cout << "no match found\n"; });  // match fallback

Key Rules:

  • __ + .end(): Use for exhaustive data types (integers, enums, etc.) where all possible cases are known
  • .otherwise(): Use when matching is not guaranteed to be exhaustive (complex structures, variants, etc.)
  • Cannot mix: __/.end() and .otherwise() cannot be used in the same match expression

10. When to Use Pattern Fallback vs Match Fallback

Choose the right fallback strategy based on your data type:

// ✅ For exhaustive types - use __ + end()
enum class Color { Red, Green, Blue };
match(color)
  .when(lit(Color::Red) >> [] { /* ... */ })
  .when(lit(Color::Green) >> [] { /* ... */ })
  .when(__ >> [] { /* handles Color::Blue */ })
  .end();

// ✅ For non-exhaustive types - use otherwise()
match(variant_value)
  .when(some_pattern >> handler)
  .otherwise(fallback_handler);

// ❌ WRONG - cannot mix both
match(value)
  .when(pattern >> handler)
  .when(__ >> [] { /* ... */ })
  .otherwise([] { /* ... */ });  // Compile error!

11. When to Use Patternia

Patternia is particularly effective when:

  • branching depends on data shape, not just values
  • conditions involve relationships between multiple fields
  • if / switch logic becomes deeply nested
  • you want expression-oriented control flow

For a deeper dive, see the API Reference and Design Guide.