# matchit.cpp and pattern matching in C++ without macros

> match(it) is a single-header C++17 pattern matching library with no heap allocation and no macros in its API, built around composable patterns and a match(VALUE)(pattern | ... = handler) call. It is the closest C++ has come to Rust-style pattern matching without a compiler proposal, and the README is candid about the trade-offs.

**BowenFu/matchit.cpp** — match(it): A lightweight single-header pattern-matching library for C++17 with macro-free APIs.

- Repository: https://github.com/BowenFu/matchit.cpp
- Website: https://bowenfu.github.io/matchit.cpp
- Stars: 653 · Forks: 21
- Language: C++
- License: Apache-2.0
- Published: 2026-10-21 · Updated: 2026-10-21 · Language: en
- Canonical page: https://hysenlabs.com/projects/bowenfu-matchit-cpp

## A header file, no allocation, and a match call that looks like a function

match(it) is described as a lightweight header-only pattern-matching library for C++17 with macro-free APIs. The header is matchit.h, and the design goal of the whole thing is stated in the title: pattern matching, without the macros that the C++ proposals for it assumed.

The core syntax is a function call:

```cpp
match(VALUE)
(
    pattern | PATTERN1 = HANDLER1,
    pattern | PATTERN2 = HANDLER2,
    ...
)
```

It returns some value returned by the handlers, and the return type is the common type for all of them. It is void if all the handlers do not return values, and incompatible return types from multiple handlers is a compile error. When handlers return values, the patterns must be exhaustive, and a runtime error happens if none of the patterns match.

Two details follow from the shape of the call. A handler can be a value or an Id variable, so 1 is equivalent to []{return 1;}. And the wildcard _ matches any value, and the README recommends always using it as the last pattern, playing the same role as the default case does for a switch statement, to avoid case escaping.

The headers of the feature list are the ones that decide whether this belongs in your project. Single header library, macro-free APIs, and no heap memory allocation. On top of that, no external dependencies, and portability that is described as continuously tested under Ubuntu, MacOS and Windows using GCC, Clang and MSVC. Reliability is described as a strict compiler checking option plus sanitizers plus valgrind.

The no heap allocation point is the one worth pausing on, because it is the constraint that shapes the whole design. A pattern matching facility that cannot allocate means the pattern and the match target have to be described in ways that do not need storage of their own, and that is a large part of why the API is shaped the way it is.

## Installing it is one of five routes, and the CMake one is a warning

The README offers five installation options, and the second one contains the most important sentence in the document.

Option one is to download the header. The file is at a raw GitHub URL, you put it in your include directory for dependencies, and the README says that is it. If you want a command line:

```bash
wget https://raw.githubusercontent.com/BowenFu/matchit.cpp/main/include/matchit.h
```

Option two is CMake FetchContent, which is what most projects would use. The snippet declares the dependency with a Git repository and a Git tag:

```cmake
include(FetchContent)

FetchContent_Declare(
    matchit
    GIT_REPOSITORY https://github.com/BowenFu/matchit.cpp.git
    GIT_TAG main)

FetchContent_GetProperties(matchit)
if(NOT matchit_POPULATED)
    FetchContent_Populate(matchit)
    add_subdirectory(${matchit_SOURCE_DIR} ${matchit_BINARY_DIR}
                    EXCLUDE_FROM_ALL)
endif()
```

You then add the source directory's include path, and the README adds a warning that matters more than it looks: replace main with the latest release tag to avoid API compatibility breaking. The example uses main, and following it verbatim means your build is coupled to whatever is on the branch, including breaking changes. Pinning a tag is a two-word change with real consequences, and the README tells you to do it and then shows an example that does not.

Option three is find_package, which needs a clone, a cmake configure into a build directory and make install, and the README shows the three commands. Option four is vcpkg, one command, and the support was contributed by a named contributor. Option five is Conan, and the library has been submitted to the Conan Center Index, again with the contribution credited.

So the library is packaged for all the usual routes, and the only thing standing between a careful user and a broken build is the Git tag in the FetchContent snippet.

## Patterns compose, and users can write their own

The extensibility claim is specific: users can define their own patterns, either by composing existing ones or by creating brand new ones, and the feature list calls the patterns composable.

The samples directory in the repository shows the breadth of what is available. There are samples for literal patterns, identifier patterns, matching integrals, matching strings, matching tuples, matching variants, matching polymorphic types, matching enums and class hierarchies, destructuring enums, destructuring nested structs and enums, the rest pattern, the reference pattern, the dereference pattern, the extractor pattern, the predicate based discriminator, the matcher within, range patterns, slice patterns, extra conditionals with match guards, conditional if-let expressions, evaluating expression trees, and a red-black tree rebalancing sample.

That list is the real documentation of the library's capability. The samples are named after the pattern kinds they demonstrate, and the if-let expressions and match guards samples show that the library is not only about dispatching on a value's shape but also about binding values conditionally and adding predicates to a case.

The distinction the library makes between patterns and matchers is worth understanding from the sample names alone. A pattern describes what a value looks like, and a matcher is something that can be applied to a value to see if it matches, which is what makes predicate based discrimination and match guards possible.

The README also states support for destructuring tuple-like and range-like containers, and partial support for constant expression. The samples for range patterns, slice patterns and rest patterns are where the tuple and range destructuring shows up in practice, and the rest pattern is what lets you take a prefix or a tail from a sequence and bind the remainder.

## constexpr works for some patterns, and the factorial sample shows it

The hello world sample is a constexpr factorial function, and it is worth reading because it shows several features at once:

```cpp
#include "matchit.h"

constexpr int32_t factorial(int32_t n)
{
    using namespace matchit;
    assert(n >= 0);
    return match(n)(
        pattern | 0 = 1,
        pattern | _ = [n] { return n * factorial(n - 1); }
    );
}
```

The base case is a literal pattern matching 0 with the handler being the value 1, and the recursive case is the wildcard with a handler that captures n and calls factorial again.

The multi-value sample is a gcd, and it shows destructuring:

```cpp
constexpr int32_t gcd(int32_t a, int32_t b)
{
    using namespace matchit;
    return match(a, b)(
        pattern | ds(_, 0) = [&] { return a >= 0 ? a : -a; },
        pattern | _        = [&] { return gcd(b, a%b); }
    );
}

static_assert(gcd(12, 6) == 6);
```

The static_assert is the point. Some patterns support constexpr match, meaning they can be matched at compile time, and this sample demonstrates that gcd(12, 6) is evaluated by the compiler rather than at runtime.

The partial support wording is honest and worth heeding. Not every pattern composes into a constant expression, and a library that says partial support is telling you there are cases where you will fall back to runtime evaluation. If your use case depends on compile time evaluation, the samples directory and the reference document are where you would find out which patterns qualify.

One more thing the README points out is that, unlike pattern matching in other languages, variables can be used normally inside patterns. That is a small ergonomic difference that matters when you write a real case analysis rather than a toy.

## Debugging advice from the maintainer, and the documents that explain the design

The Tips for Debugging section is short and unusually practical, which tells you something about how the library is meant to be used.

The advice is to write the body of a lambda on separate lines so that you can set breakpoints in it, and it shows the difference between the two forms. The debugging-friendly version is:

```cpp
pattern | xyz = [&]
{
    // Separate lines for function body <- set break points here
}
```

and the version to avoid puts the body on one line, which is much more debugging-friendly compared to a lambda whose body is inline, because you will end up debugging into the library.

The recommendation that follows is not to debug into the library unless you really decide to root cause or fix a bug in it, just as you would not debug into an STL variant or ranges. That is a well placed expectation. A pattern matching library is the layer where your case analysis is turned into control flow, and a debugger stepping through its internals is a sign the match expression is too complex, not that the library is broken.

The other instruction is to create a minimal sample to reproduce an issue, and the README notes that a minimal sample lets you root cause the issue more quickly, and that you can open an issue in the repository and attach the minimal sample code, with an expectation of a response, sometimes after a day or two.

The design documentation is in the repository too. REFERENCE.md holds the syntax design details, From-Rust-to-matchit.md gives equivalent samples for corresponding Rust code, and From-Pattern-Matching-Proposal-to-matchit.md gives equivalent samples for the C++ pattern matching proposal and, in the README's words, shows the pros and cons of the library over the proposal. There is also a Proposal.md and a PDF called A taste of pattern matching in C++ at the repository root.

That last comparison is the one to read if you are deciding between waiting for the language feature and using this library today.

## Where matchit.cpp is the wrong tool

The first wrong case is a project on C++20 or later. The library targets C++17, and the standard badge in the README states C++17/20, which is a range that tells you the floor and the tested ceiling rather than a promise about newer standards. A team already on C++20 with concepts and ranges is working in a different world from the one this header was written for, and the library's own debugging advice tells you to treat it like an STL variant rather than something you step into.

The second wrong case is wanting pattern matching to be a language feature. This library is a library, and the trade is explicit in the repository: the From-Pattern-Matching-Proposal-to-matchit.md document exists to show the pros and cons of the library over the proposal. If your organisation is waiting for the standard, or if you would rather have compiler diagnostics for a non-exhaustive match than a runtime error, then a library is the wrong shape of solution. The library's own documentation says that when handlers return values the patterns must be exhaustive and that a runtime error happens if they do not match, which is a weaker guarantee than a compile error.

The third wrong case is a hot path where the overhead of the match machinery matters more than the clarity. The no heap allocation promise means the library is not allocating per match, which is what makes it usable in a performance-sensitive context, but the README does not claim the generated control flow is as fast as a hand written switch, and a project that has already measured that difference knows it.

The alternative worth naming is the language proposal itself, and the difference in approach is exactly the difference between a library and a language feature. A proposal gives you pattern matching as part of the language, with the compiler checking exhaustiveness and with the standard library's own patterns, but it is not in the compiler you are using today. This library gives you the same shape of code now, in a header you can vendor, with the trade that the checking is a runtime error rather than a diagnostic, that the syntax is the library's own rather than the standard's, and that you are on C++17. The library also has a document mapping Rust code onto its syntax, which is a reasonable proxy for how a Rust developer would find the API, and a document mapping the proposal onto it, which is the fairest way to judge it against the future.

## Maintenance state and what the release history says about the API

The repository is not archived and the last push was on 2026-10-14, so the work is current. The tagged releases are much older: v0.1.3 on 2021-10-30, v1.0.0 on 2022-08-13 described as the first stable release, and v1.0.1 on 2022-08-25.

So the source tree has moved a long way past the last tag, and the README's instruction to replace main with the latest release tag means exactly this: the release tag is a much older API than the branch. That is a genuine tension in the project. The FetchContent example points at main, which tracks the current work, and the warning against main is about API compatibility breaking. Neither choice is free.

The licence is Apache-2.0, with the LICENSE file at the repository root. For a single-header library that vendors into your build, the permissive terms and the patent grant are the parts that matter, and the fact that the recommended first option is a single wget of one file means the licensing question is easy to answer, since the file carries its own header.

The build and test infrastructure is visible in the repository. There is a CMakeLists.txt, a cmake directory, a single-header.sh script, which is presumably what generates the amalgamated header from the include tree, a test directory, a develop directory, a sample directory with its own CMakeLists.txt, a .clang-format file, and a CODE_OF_CONDUCT.md. The CI badges show two workflows, one for CMake and one for sanitizers, and there is a Codecov badge on the main branch.

The single-header.sh script is worth knowing about if you intend to vendor the library, because it tells you the amalgamated header is generated rather than hand maintained, which means the header you download and the include tree are the same code and you can pick either.

## Conclusion

matchit.cpp fits a C++17 codebase that wants Rust-style pattern matching today, particularly for variant, tuple and enum dispatch, and that is willing to include one header file rather than wait for a language feature. It is the wrong choice if you need C++20 or later features, since the library targets C++17, and the wrong choice for a project that wants pattern matching as a language feature with compiler diagnostics rather than as a library. Before you adopt it, read REFERENCE.md for the syntax, check that the patterns you need are in the sample directory, and pin a release tag rather than tracking main, because the README itself warns that main can break API compatibility.

## FAQ

### How do I install matchit.cpp?

There are five documented routes: download the matchit.h header and put it in your include directory, use CMake FetchContent, install it and use find_package, install it with vcpkg, or install it with Conan. The FetchContent snippet uses GIT_TAG main, and the README advises replacing main with the latest release tag to avoid API compatibility breaking.

### What does matchit.cpp require from the compiler?

C++17. The library is header-only, has no external dependencies, and is described as continuously tested under Ubuntu, MacOS and Windows using GCC, Clang and MSVC.

### Does matchit.cpp support compile time pattern matching?

Partially. The README says some patterns support constexpr match, so you can match them at compile time, and the gcd sample ends in a static_assert(gcd(12, 6) == 6) to demonstrate that the match is evaluated at compile time.

### What happens if no pattern matches in matchit.cpp?

When handlers return values the patterns must be exhaustive, and a runtime error happens if all patterns do not get matched. It is not an error if the handlers' return types are all void, and incompatible return types from multiple handlers is a compile error.

### Does matchit.cpp allocate memory or use macros?

The feature list states no heap memory allocation and macro-free APIs, and the library is a single header. It also has no external dependencies.

## Sources

- [BowenFu/matchit.cpp on GitHub](https://github.com/BowenFu/matchit.cpp)
- [License: Apache-2.0](https://github.com/BowenFu/matchit.cpp/blob/main/LICENSE)
- [Project website](https://bowenfu.github.io/matchit.cpp)
- [README](https://github.com/BowenFu/matchit.cpp/blob/main/README.md)
- [Releases](https://github.com/BowenFu/matchit.cpp/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/bowenfu-matchit-cpp
