# How to Create Custom Matchers for Custom Assertions in Catch2

> Learn to create custom matchers for custom assertions in Catch2. Extend MatcherBase or MatcherGenericBase, implement match and describe for powerful, readable tests.

- Repository: [Catch Org/Catch2](https://github.com/catchorg/Catch2)
- Tags: how-to-guide
- Published: 2026-07-29

---

**Creating custom matchers in Catch2 involves deriving from `MatcherBase<T>` for type-specific matching or `MatcherGenericBase` for generic range matching, implementing the `match()` and `describe()` methods, and optionally providing factory functions for clean syntax.**

Catch2’s matcher subsystem allows you to write expressive, domain-specific assertions that keep test code readable while handling complex validation logic. Whether you need to verify numeric ranges, compare containers, or validate custom data structures, custom matchers provide a type-safe extension mechanism. This guide walks through the two architectural styles available in the Catch2 repository and shows how to implement them correctly.

## Understanding the Matcher Architecture

The matcher system in `catchorg/Catch2` consists of three distinct layers working together to provide both flexibility and performance.

**Factory functions** provide type deduction and uniform call-site syntax (e.g., `IsBetween(1, 10)`), allowing users to write `REQUIRE_THAT(value, Matcher())` without explicit template parameters.

**Matcher base classes** define the polymorphic interface used by the framework. Old-style matchers inherit from `Catch::Matchers::MatcherBase<T>` defined in [`src/catch2/matchers/catch_matchers.hpp`](https://github.com/catchorg/Catch2/blob/main/src/catch2/matchers/catch_matchers.hpp), while new-style matchers use `Catch::Matchers::MatcherGenericBase` from [`src/catch2/matchers/catch_matchers_templated.hpp`](https://github.com/catchorg/Catch2/blob/main/src/catch2/matchers/catch_matchers_templated.hpp).

**Combination operators** (`&&`, `||`, `!`) enable building composite matchers through `MatchAllOf`, `MatchAnyOf`, and `MatchNotOf` classes, also declared in [`src/catch2/matchers/catch_matchers.hpp`](https://github.com/catchorg/Catch2/blob/main/src/catch2/matchers/catch_matchers.hpp). These operators work only on lvalue matchers that outlive the combined expression to prevent use-after-free errors.

## Old-Style Matchers with MatcherBase

Old-style matchers derive from `MatcherBase<T>` and offer optimal compile-time performance for specific types. This approach works best when you know the exact type being matched at compile time.

### Implementation Requirements

To create an old-style matcher, inherit from `Catch::Matchers::MatcherBase<T>` and override two virtual functions:

1. `bool match(T const& arg) const` – returns `true` when the value satisfies your condition
2. `std::string describe() const` – returns a human-readable description of what the matcher checks

Always provide a factory function to hide template instantiation details from test authors.

```cpp
#include <catch2/catch_test_macros.hpp>
#include <catch2/matchers/catch_matchers.hpp>
#include <sstream>

template <typename T>
class IsBetweenMatcher : public Catch::Matchers::MatcherBase<T> {
    T m_low, m_high;
public:
    IsBetweenMatcher(T low, T high) : m_low(low), m_high(high) {}

    bool match(T const& value) const override {
        return value >= m_low && value <= m_high;
    }

    std::string describe() const override {
        std::ostringstream oss;
        oss << "is between " << m_low << " and " << m_high;
        return oss.str();
    }
};

template <typename T>
IsBetweenMatcher<T> IsBetween(T low, T high) {
    return { low, high };
}

// Usage in tests
TEST_CASE("Old-style custom matcher") {
    CHECK_THAT(5, IsBetween(1, 10));
    CHECK_THAT(-3.5, IsBetween(-4.0, -2.0));
}

```

The `describe()` method generates failure messages like "5 is between 1 and 10" when assertions fail, making test output immediately actionable.

## New-Style Matchers with MatcherGenericBase

New-style matchers inherit from `Catch::Matchers::MatcherGenericBase` and support templated `match()` functions, enabling polymorphic matching across different container types or ranges.

### Templated Matching for Ranges

Unlike `MatcherBase<T>`, which locks you to a specific type, `MatcherGenericBase` allows the `match` function to accept arguments by value or as template parameters. This is essential when writing matchers that work with any range-like type (vectors, arrays, lists) simultaneously.

```cpp
#include <catch2/catch_test_macros.hpp>
#include <catch2/matchers/catch_matchers_templated.hpp>
#include <algorithm>

template <typename Range>
struct EqualsRangeMatcher : Catch::Matchers::MatcherGenericBase {
    explicit EqualsRangeMatcher(Range const& ref) : m_ref(ref) {}

    template <typename OtherRange>
    bool match(OtherRange const& oth) const {
        using std::begin; using std::end;
        return std::equal(begin(m_ref), end(m_ref), begin(oth), end(oth));
    }

    std::string describe() const override {
        return "equals range " + Catch::rangeToString(m_ref);
    }

private:
    Range const& m_ref;
};

template <typename Range>
auto EqualsRange(Range const& r) -> EqualsRangeMatcher<Range> {
    return EqualsRangeMatcher<Range>{ r };
}

// Usage demonstrating cross-type comparison
TEST_CASE("New-style custom matcher") {
    std::vector<int> expected{1, 2, 3};

    REQUIRE_THAT(std::array<int, 3>{ {1, 2, 3} }, EqualsRange(expected));
    
    // Composite matcher example
    REQUIRE_THAT(std::list<int>{4, 5, 6},
                 EqualsRange(std::vector<int>{4, 5, 6}) ||
                 EqualsRange(std::array<int, 3>{ {4, 5, 6} }));
}

```

As implemented in [`src/catch2/matchers/catch_matchers_templated.hpp`](https://github.com/catchorg/Catch2/blob/main/src/catch2/matchers/catch_matchers_templated.hpp), `MatcherGenericBase` removes the requirement to specify a single matched type upfront, making your matchers more reusable across the codebase.

## Composing Matchers and Managing Lifetimes

Catch2 supports combining matchers with `&&`, `||`, and `!` operators, but these combinations create objects that store references to their constituent matchers.

### Safe Usage Patterns

When using composite matchers in a single expression, temporaries live long enough for the check to complete safely:

```cpp
auto closeToZero = Catch::Matchers::WithinAbs(0, 0.01);
auto notZero = !Catch::Matchers::WithinULP(0., 1);

CHECK_THAT(value, closeToZero && notZero);  // Safe: temporaries live for the full expression

```

However, storing a combined matcher requires ensuring all sub-matchers outlive the composite:

```cpp
// Store components first to extend their lifetime
auto isClose = Catch::Matchers::WithinAbs(0, 0.01);
auto isNotZero = !Catch::Matchers::WithinULP(0., 1);

auto combined = isClose && isNotZero;  // Safe: both parts outlive 'combined'
CHECK_THAT(value, combined);

```

The operators return `MatchAllOf`, `MatchAnyOf`, or `MatchNotOf` objects defined in [`src/catch2/matchers/catch_matchers.hpp`](https://github.com/catchorg/Catch2/blob/main/src/catch2/matchers/catch_matchers.hpp) that hold references to the original matchers. Storing a combined matcher while allowing its components to go out of scope results in undefined behavior due to dangling references.

## Summary

- **Old-style matchers** derive from `MatcherBase<T>` in [`src/catch2/matchers/catch_matchers.hpp`](https://github.com/catchorg/Catch2/blob/main/src/catch2/matchers/catch_matchers.hpp) for type-specific validation with minimal overhead.
- **New-style matchers** derive from `MatcherGenericBase` in [`src/catch2/matchers/catch_matchers_templated.hpp`](https://github.com/catchorg/Catch2/blob/main/src/catch2/matchers/catch_matchers_templated.hpp) to support templated `match()` functions working across multiple container types.
- Every custom matcher must implement `bool match(Arg const& arg) const` and `std::string describe() const` to satisfy the framework interface.
- Factory functions (e.g., `IsBetween()`, `EqualsRange()`) provide type deduction and clean syntax for test authors.
- Composite matchers created with `&&`, `||`, and `!` store references to their operands; ensure component matchers outlive any stored combined objects.

## Frequently Asked Questions

### What is the difference between MatcherBase and MatcherGenericBase?

`MatcherBase<T>` requires you to specify the matched type as a template parameter and implements `match()` as a virtual function, making it ideal for specific types and slightly faster to compile. `MatcherGenericBase` allows `match()` to be a template method accepting any type, enabling matchers that work with ranges, containers, or polymorphic types without upfront type constraints.

### Can I combine custom matchers with built-in Catch2 matchers?

Yes. All matchers in Catch2, whether custom or built-in, support the `&&`, `||`, and `!` operators as long as they inherit from the appropriate base class. You can write expressions like `REQUIRE_THAT(value, MyCustomMatcher() || Catch::Matchers::WithinAbs(0, 0.1))` provided you manage the lifetime of stored matcher objects correctly.

### Where should I place my custom matcher definitions?

Define custom matchers in a header file included by your test files, or directly in the test translation unit if only needed locally. Ensure you include `<catch2/matchers/catch_matchers.hpp>` for old-style matchers or `<catch2/matchers/catch_matchers_templated.hpp>` for new-style matchers. Factory functions should be placed in a namespace to avoid global name pollution.

### Why does my composite matcher cause a crash or undefined behavior?

This occurs when a combined matcher (created with `&&`, `||`, or `!`) outlives its constituent parts. According to the Catch2 source in [`src/catch2/matchers/catch_matchers.hpp`](https://github.com/catchorg/Catch2/blob/main/src/catch2/matchers/catch_matchers.hpp), these operators return objects storing const references to the original matchers. If you store `auto combined = matcher1 && matcher2` while `matcher1` or `matcher2` are temporaries that get destroyed, the stored references become dangling. Store sub-matchers as named variables with longer lifetimes before combining them.