Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

match(it): A lightweight single-header pattern-matching library for C++17 with macro-free APIs.

License

NotificationsYou must be signed in to change notification settings

BowenFu/matchit.cpp

Repository files navigation

match(it).cpp

StandardTypeType

PlatformPlatformPlatform

CMakeCMakeGitHub licensecodecov

Features

  • Easy to get started.

    • godbolt
  • Single header library.

  • Macro-free APIs.

  • No heap memory allocation.

  • Portability: continuously tested under Ubuntu, MacOS and Windows using GCC/Clang/MSVC.

  • No external dependencies.

  • Reliability : strict compiler checking option + sanitizers + valgrind.

  • Composable patterns.

  • Extensible, users can define their own patterns, either via composing existent ones, or create brand new ones.

  • Support destructing tuple-like and range-like containers.

  • Partial support for constant expression.

Installation

Option 1. Downloadmatchit.h

Simply download the header filematchit.h and put it in your include directory for dependencies.

That's it.

You can download via this bash command

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

Option 2. Manage with cmake FetchContent

Include the code snippet in your CMakeLists.txt:

include(FetchContent)FetchContent_Declare(matchitGIT_REPOSITORYhttps://github.com/BowenFu/matchit.cpp.gitGIT_TAGmain)FetchContent_GetProperties(matchit)if(NOTmatchit_POPULATED)FetchContent_Populate(matchit)add_subdirectory(${matchit_SOURCE_DIR}${matchit_BINARY_DIR}EXCLUDE_FROM_ALL)endif()message(STATUS"Matchit header are present at${matchit_SOURCE_DIR}")

And add${matchit_SOURCE_DIR}/include to your include path.

Replacemain with latest release tag to avoid API compatibility breaking.

Option 3. Manage with cmake find_package

Clone the repo via

git clone --depth 1 https://github.com/BowenFu/matchit.cpp

Install the library via

cd matchit.cppcmake -B ./buildcd buildmake install

Then use find_package in your CMakeLists.txt.

Option 4. Manage with vcpkg

(Thanks to @daljit97 for adding the support.)

vcpkg install matchit

Option 5. Manage with conan

Now the library has been submitted toConan Center Index.

You can now install the library via conan.

(Thanks to @sanblch for adding the support.)

Tips for Debugging

To make your debugging easier, try to write your lambda function body in separate lines so that you can set break points in it.

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

is much more debugging-friendly compared to

pattern | xyz = [&] {/* some codes here*/ },// <- Set break points here, you will debug into the library.

Do not debug into this library unless you really decide to root cause / fix some bugs in this library, just like you won't debug into STL variant or ranges.

Please try to create a minimal sample to reproduce the issues you've met. You can root cause the issue more quickly in that way.

You can also create an issue in this repo and attach the minimal sample codes and I'll try to response as soon as possible (sometimes please expect one or two days delay).

Syntax Design

For syntax design details please refer toREFERENCE.

From Rust to match(it)

The documentFrom Rust to match(it) gives equivalent samples for corresponding Rust samples.

There you may have a picture of what coding withmatch(it) would be like.

From Pattern Matching Proposal to match(it)

The documentFrom Pattern Matching Proposal to match(it) gives equivalent samples for corresponding samples in the Match Pattern Matching Proposal.

There you will see the pros and cons of the library over the proposal.

Quick start.

Let's start a journey on the library!

(For complete samples, please refer tosamples directory.)

Hello World!

The following sample shows to how to implement factorial usingmatch(it) library.

#include"matchit.h"constexprint32_tfactorial(int32_t n){usingnamespacematchit;assert(n >=0);returnmatch(n)(        pattern |0 =1,        pattern | _ = [n] {return n *factorial(n -1); }    );}

Thebasic syntax for pattern matching is

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

This is a function call and will return some value returned by handlers. The return type is the common type for all handlers. Return type will be void if all handlers do not return values. Incompatible return types from multiple handlers is a compile error.When handlers return values, the patterns must be exhaustive. A runtime error will happen if all patterns do not get matched.It is not an error if handlers' return types are all void.

The handler can also be a value or an Id variable.1 is equivalent to[]{return 1;}.

The wildcard_ will match any values. It is a common practice to always use it as the last pattern, playing the same role in our library asdefault case does forswitch statements, to avoid case escaping.

We can matchmultiple values at the same time:

#include"matchit.h"constexprint32_tgcd(int32_t a,int32_t b){usingnamespacematchit;returnmatch(a, b)(        pattern |ds(_,0) = [&] {return a >=0 ? a : -a; },        pattern | _        = [&] {returngcd(b, a%b); }    );}static_assert(gcd(12,6) == 6);

Note that some patterns support constexpr match, i.e. you can match them at compile time. From the above code snippets, we can see thatgcd(12, 6) can be executed in compile time.

Different from matching patterns in other programming languages,variables can be used normally inside patterns inmatch(it), this is shown in the following sample:

#include"matchit.h"#include<map>template<typename Map,typename Key>constexprboolcontains(Mapconst& map, Keyconst& key){usingnamespacematchit;returnmatch(map.find(key))(        pattern | map.end() =false,        pattern | _         =true    );}

Hello Moon!

We can usePredicate Pattern to put some restrictions on the value to be matched.

constexprdoublerelu(double value){returnmatch(value)(        pattern | (_ >=0) = value,        pattern | _        =0    );}static_assert(relu(5) == 5);static_assert(relu(-5) == 0);

We overload some operators for wildcard symbol_ to facilitate usage of basic predicates.

Sometimes we want to share one handler for multiple patterns,Or Pattern is the rescue:

#include"matchit.h"constexprboolisValid(int32_t n){usingnamespacematchit;returnmatch(n)(        pattern |or_(1,3,5) =true,        pattern | _            =false    );}static_assert(isValid(5));static_assert(!isValid(6));

And Pattern is for combining multiple Predicate patterns.

App Pattern is powerful when you want to extract some information from the subject.Its syntax is

app(PROJECTION, PATTERN)

A simple sample to check whether a num is large can be:

#include"matchit.h"constexprboolisLarge(double value){usingnamespacematchit;returnmatch(value)(        pattern |app(_ * _, _ >1000) =true,        pattern | _                    =false    );}// app with projection returning scalar types is supported by constexpr match.static_assert(isLarge(100));

Note that_ * _ generates a function object that computes the square of the input, can be considered the short version of[](auto&& x){ return x*x;}.

Can we bind the value if we have already extract them? Sure,Identifier Pattern is for you.

Let's log the square result, with Identifier Pattern the codes would be

#include<iostream>#include"matchit.h"boolcheckAndlogLarge(double value){usingnamespacematchit;    Id<double> s;returnmatch(value)(        pattern |app(_ * _, s.at(_ >1000)) = [&] {                std::cout << value <<"^2 =" << *s <<" > 1000!" << std::endl;returntrue; },        pattern | _ =false    );}

To use Identifier Patterns,we need to define/declare the identifiers (Id<double> s) first. (Do not mark it as const.)This can be a little strange if you've use Identifier Patterns in other programming language. This is due to the language restriction.But don't be upset. This added verbosity makes it possible for us touse variables inside patterns. You may never be able to do this in other programming language.

Here* operator is used to dereference the value inside identifiers.One thing to note is that identifiers are only valid insidematch scope. Do not try to dereference it outside.

Id::at is similar to the@ pattern in Rust, i.e., bind the value when the subpattern gets matched.

Also note when the same identifier is bound multiple times, the bound values must equal to each other viaoperator==.An sample to check if an array is symmetric:

#include"matchit.h"constexprboolsymmetric(std::array<int32_t,5>const& arr){usingnamespacematchit;    Id<int32_t> i, j;returnmatch(arr)(        pattern |ds(i, j, _, j, i) =true,        pattern | _                 =false    );}static_assert(symmetric(std::array<int32_t,5>{5,0,3,7,10}) == false);static_assert(symmetric(std::array<int32_t,5>{5,0,3,0,5}) == true);static_assert(symmetric(std::array<int32_t,5>{5,1,3,0,5}) == false);

Hello Mars!

Now we come to the most powerful parts:Destructure Pattern.Destructure Pattern can be used forstd::tuple,std::pair,std::array (fixed-sized containers), and dynamic containers or sized ranges(std::vector,std::list,std::set, and so on) withstd::begin andstd::end supports.

The outermostds inside pattern can be omitted. When pattern receives multiple parameters, they are treated as subpatterns of a ds pattern.

#include"matchit.h"template<typename T1,typename T2>constexprautoeval(std::tuple<char, T1, T2>const& expr){usingnamespacematchit;    Id<T1> i;    Id<T2> j;returnmatch(expr)(        pattern |ds('+', i, j) = i + j,        pattern |ds('-', i, j) = i - j,        pattern |ds('*', i, j) = i * j,        pattern |ds('/', i, j) = i / j,        pattern | _ = []        {assert(false);return -1;        });}

Some operators have been overloaded forId, soi + j will return a nullary function that return the value of*i + *j.

There also ways to destructure your struct / class, make your struct / class tuple-like or adopt App Pattern.The second option looks like

// Another option to destructure your struct / class.constexprautodsByMember(DummyStructconst&v){usingnamespacematchit;// compose patterns for destructuring struct DummyStruct.constexprauto dsA =dsVia(&DummyStruct::size, &DummyStruct::name);    Id<charconst*> name;returnmatch(v)(        pattern |dsA(2, name) = name,        pattern | _ ="not matched"    );};static_assert(dsByMember(DummyStruct{1,"123"}) == std::string_view{"not matched"});static_assert(dsByMember(DummyStruct{2,"123"}) == std::string_view{"123"});

Let's continue the journey.Sometimes you have multiple identifiers and you want exert a restriction on the relationship of them. Is that possible?Sure! Here comes theMatch Guard. Its syntax is

pattern | PATTERN | when(GUARD) = HANDLER

Say, we want to match only when the sum of two identifiers equal to some value, we can write codes as

#include<array>#include"matchit.h"constexprboolsumIs(std::array<int32_t,2>const& arr,int32_t s){usingnamespacematchit;    Id<int32_t> i, j;returnmatch(arr)(        pattern |ds(i, j) |when(i + j == s) =true,        pattern | _                           =false    );}static_assert(sumIs(std::array<int32_t,2>{5,6},11));

That is cool, isn't it?Note thati + j == s will return a nullary function that return the result of*i + *j == s.

Now we come to theOoo Pattern. What is that? You may ask. In some programming language it's calledRest Pattern.You can match arbitrary number of items with it. It can only be used insideds patterns though and at most one Ooo pattern can appear inside ads pattern.You can write the code as following when you want to check pattern of a tuple.

#include<array>#include"matchit.h"template<typename Tuple>constexprint32_tdetectTuplePattern(Tupleconst& tuple){usingnamespacematchit;returnmatch(tuple)    (        pattern |ds(2, ooo,2)  =4,        pattern |ds(2, ooo   )  =3,        pattern |ds(ooo,2   )  =2,        pattern |ds(ooo      )  =1    );}static_assert(detectTuplePattern(std::make_tuple(2,3,5,7,2)) == 4);

What is more, we canbind a subrange to the ooo pattern when destructuring astd::array or other containers / ranges.That is quite cool.We can check if an/aarray/vector/list/set/map/subrange/... is symmetric with:

template<typename Range>constexprboolrecursiveSymmetric(Rangeconst &range){    Id<int32_t> i;    Id<SubrangeT<Rangeconst>> subrange;returnmatch(range)(        pattern |ds(i, subrange.at(ooo), i) = [&] {returnrecursiveSymmetric(*subrange); },        pattern |ds(_, ooo, _)              =false,        pattern | _                          =true    );

In the first pattern, we require that the head equals to the end. and if that is the case, we further check the rest parts (bound to subrange) via a recursive call.Once some nested call fails to meet that requirement (fall through to the second pattern), the checking fails.Otherwise when there are only one element left or the range size is zero, the last pattern gets matched, we return true.

Hello Sun!

We've done with our core patterns. Now let's start the journey ofcomposing patterns.

You should be familiar withSome Pattern andNone Pattern if you have used the pattern matching feature in Rust.

Some / None Patterns can be used to match raw pointers,std::optional,std::unique_ptr,std::shared_ptr and other types that can be converted to bool and dereferenced.A typical sample can be

#include"matchit.h"template<typename T>constexprautosquare(std::optional<T>const& t){usingnamespacematchit;    Id<T> id;returnmatch(t)(        pattern |some(id) = id * id,        pattern | none     =0    );}constexprauto x = std::make_optional(5);static_assert(square(x) == 25);

Some pattern accepts a subpattern. In the sample the subpattern is an identifier and we bind the dereferenced result to it.None pattern is alone.

Some and none patterns are not atomic patterns inmatch(it), they are composed via

template<typename T>constexprauto cast = [](auto && input) {returnstatic_cast<T>(input);};constexprauto deref = [](auto &&x) {return *x; };constexprauto some = [](autoconst pat) {returnand_(app(cast<bool>,true),app(deref, pat));};constexprauto none = app(cast<bool>,false);

For Some pattern, first we cast the value to a boolean value, if the boolean value is true, we can further dereference it. Otherwise, the match fails.For None pattern we simply check if the converted boolean value is false.

As pattern is very useful for handlingsum type, including class hierarchies,std::variant, andstd::any.std::variant andstd::any can be visited as

#include"matchit.h"template<typename T>constexprautogetClassName(Tconst& v){usingnamespacematchit;returnmatch(v)(        pattern | as<charconst*>(_) ="chars",        pattern | as<int32_t>(_)     ="int32_t"    );}constexpr std::variant<int32_t,charconst*> v =123;static_assert(getClassName(v) == std::string_view{"int32_t"});

Class hierarchies can be matched as

structShape{virtual~Shape() =default;};structCircle : Shape {};structSquare : Shape {};autogetClassName(Shapeconst &s){returnmatch(s)(        pattern | as<Circle>(_) ="Circle",        pattern | as<Square>(_) ="Square"    );}

As pattern is not an atomic pattern, either. It is composed via

template<typename T>constexpr AsPointer<T> asPointer;template<typename T>constexprauto as = [](autoconst pat) {returnapp(asPointer<T>,some(pat));};

For classes,dynamic_cast is used by default for As pattern, but we can change the behavior through theCustomization Point.Users can customize the down casting via defining aget_if function for their classes, similar tostd::get_if forstd::variant:

#include<iostream>#include"matchit.h"enumclassKind {kONE,kTWO };classNum{public:virtual~Num() =default;virtual Kindkind()const = 0;};classOne :publicNum{public:constexprstaticauto k = Kind::kONE;    Kindkind()constoverride {return k; }};classTwo :publicNum{public:constexprstaticauto k = Kind::kTWO;    Kindkind()constoverride    {return k;    }};template<Kind k>constexprauto kind = app(&Num::kind, k);template<typename T>autoget_if(Numconst* num) {returnstatic_cast<Tconst *>(num->kind() == T::k ? num :nullptr);}int32_tstaticCastAs(Numconst& input){usingnamespacematchit;returnmatch(input)(        pattern | as<One>(_)       =1,        pattern | kind<Kind::kTWO> =2,        pattern | _                =3    );}int32_tmain(){    std::cout <<staticCastAs(One{}) << std::endl;return0;}

Hello Milky Way!

There is additionalCustomziation Point.

Users can specializePatternTraits if they want to add a brand new pattern.

Hello Black Hole!

One thing to note is thatId is not a plain type. Any copies of it are just references to it.So do not try to return it from where it is defined.

A bad case would be

autobadId(){    Id<int> x;return x;}

Returning a composed pattern including a localId is also incorrect.

autobadPattern(){    Id<int> x;returncomposeSomePattern(x);}

Good practice is to define theId close to its usage in pattern matching.

autogoodPattern(){    Id<int> x;auto somePattern =composeSomePattern(x);returnmatch(...)    (        pattern | somePattern = ...    );}

Real world use case

mathiu is a simple computer algebra system built uponmatch(it).

A simple sample ofmathiu:

autoconst x = symbol("x");autoconst e = x ^ fraction(2,3);autoconst d = diff(e, x);// prints (* 2/3 (^ x -1/3))std::cout << toString(d) << std::endl;

Projects using this library

opennask : An 80x86 assembler like MASM/NASM for the tiny OS.

If you are aware of other projects using this library, please let me know by submitting an issue or an PR.

Contact

If you have any questions or ideas regarding the library, pleaseopen an issue.

Discussions / issues / PRs are all welcome.

Related Work

match(it)'s syntax / pattern designs have been heavily influenced by these related work

Other Work

If you are interested inmatch(it), you may also be interested inhspp which brings Haskell style programming to C++.

Support this library

Please star the repo, share the repo, or sponsor one dollar to let me know this library matters.

Contributor(s)

Thanks to all for contributing code and sending in bugs.

In particular, thanks to the following contributors:

Hugo Etchegoyen (@hugoetchegoyen)

Sponsor(s)

Thanks to @e-dant for sponsoring this project.

Star History

Star History Chart

Contributors2

  •  
  •  

Languages


[8]ページ先頭

©2009-2025 Movatter.jp