{"id":13419985,"url":"https://github.com/jbandela/simple_match","last_synced_at":"2026-02-01T11:37:35.101Z","repository":{"id":29101535,"uuid":"32630883","full_name":"jbandela/simple_match","owner":"jbandela","description":"Simple header only pattern matching for c++14","archived":false,"fork":false,"pushed_at":"2021-06-15T15:30:59.000Z","size":58,"stargazers_count":223,"open_issues_count":1,"forks_count":16,"subscribers_count":16,"default_branch":"master","last_synced_at":"2024-07-31T22:52:51.159Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"C++","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"bsl-1.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/jbandela.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE_1_0.txt","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2015-03-21T12:22:14.000Z","updated_at":"2024-07-26T01:46:43.000Z","dependencies_parsed_at":"2022-09-06T05:50:31.093Z","dependency_job_id":null,"html_url":"https://github.com/jbandela/simple_match","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/jbandela/simple_match","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jbandela%2Fsimple_match","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jbandela%2Fsimple_match/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jbandela%2Fsimple_match/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jbandela%2Fsimple_match/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/jbandela","download_url":"https://codeload.github.com/jbandela/simple_match/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jbandela%2Fsimple_match/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28977330,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-02-01T11:31:13.034Z","status":"ssl_error","status_checked_at":"2026-02-01T11:30:25.558Z","response_time":56,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-07-30T22:01:23.896Z","updated_at":"2026-02-01T11:37:35.085Z","avatar_url":"https://github.com/jbandela.png","language":"C++","funding_links":[],"categories":["TODO scan for Android support in followings","Libraries"],"sub_categories":["Misc"],"readme":"# Simple, Extensible C++ Pattern Matching Library\n\nI have recently been looking at Haskell and Rust. One of the things I wanted in C++ from those languages is pattern matching.\n\nHere is an example from the Rustlang Book (http://static.rust-lang.org/doc/master/book/match.html)\n\n```rust\nmatch x {\n    1 =\u003e println!(\"one\"),\n    2 =\u003e println!(\"two\"),\n    3 =\u003e println!(\"three\"),\n    4 =\u003e println!(\"four\"),\n    5 =\u003e println!(\"five\"),\n    _ =\u003e println!(\"something else\"),\n}\n```\nThere is currently a C++ Library Mach7 that does pattern matching (https://github.com/solodon4/Mach7), however it is big, complicated, and uses a lot of macros. I wanted to see if I could use C++14 to write a simple implementation without macros.\n\nThis library is the result of that effort. If you are familiar with C++14 especially variadic templates, forwarding, and tuples, this library and implementation should be easy for you to understand and extend.\n\n## Usage\nYou will need a C++14 compiler. I have used the latest Visual C++ 2015 CTP, GCC 4.9.2, and Clang 3.5 to test this library.\n\nThe library consists of 2 headers. `simple_match.hpp` which is the core of the library, and `some_none.hpp` which contains code that lets you match on raw pointers, and unique_ptr, and shared_ptr.\n\nHere is a simple excerpt. Assume you have included simple_match.hpp\n\n```cpp\nusing namespace simple_match;\nusing namespace simple_match::placeholders;\n\nint x = 0;\n\nwhile (true) {\n\tstd::cin \u003e\u003e x;\n\tmatch(x,\n\t\t1, []() {std::cout \u003c\u003c \"The answer is one\\n\"; },\n\t\t2, []() {std::cout \u003c\u003c \"The answer is two\\n\"; },\n\t\t_x \u003c 10, [](auto\u0026\u0026 a) {std::cout \u003c\u003c \"The answer \" \u003c\u003c a \u003c\u003c \" is less than 10\\n\"; },\n\t\t10 \u003c _x \u003c 20,\t[](auto\u0026\u0026 a) {std::cout \u003c\u003c \"The answer \" \u003c\u003c a  \u003c\u003c \" is between 10 and 20 exclusive\\n\"; },\n\t\t_, []() {std::cout \u003c\u003c \"Did not match\\n\"; }\n\t);\n}\n```\n\n## Example Files\nThere are 2 files under the test directory: `test.cpp` and `cppcon-matching.cpp`. `test.cpp` contains just some simple tests of matching. `cppcon-matching.cpp`contains the example from Mach7 that was presented at cppcon.\n\n## Extending\nThere are 2 points of customization provided in namespace `simple_matcher::customization`. They are\n```cpp\ntemplate\u003cclass T, class U\u003e\nstruct matcher;\n```\n\nand\n\n```cpp\ntemplate\u003cclass Type\u003e\nstruct tuple_adapter;\n```\n## License\nLicensed under the Boost Software License.\n\n## Tutorial\n\nWe are going to assume you have the following at the top of your file\n\n```cpp\n#include \"simple_match/simple_match.hpp\"\n\n\nusing namespace simple_match;\nusing namespace simple_match::placeholders;\n```\n\nHere is how to match exactly\n\n```cpp\nint i = 0;\nmatch(i,\n\t1, [](){std::cout \u003c\u003c \"The answer is one\";}\n\t2, [](){std::cout \u003c\u003c \"The answer is two\";}\n\totherwise, [](){std::cout \u003c\u003c \"Did not match\"}\n);\n```\nThe match function will try matching from top to bottom and run the lamba corresponding to the first successful match. `otherwise` always matches, and therefore you should have it at the end. If you find `otherwise` too long, you can also use `_`. It is located in the namespace `simple_match::placeholders`\n\nMatch also works for strings.\n```\nstd::string s = \"\";\n\nmatch(s,\n\t\"Hello\", [](){ std::cout \u003c\u003c \" You said hello\\n\";},\n\t_, [](){std::cout \u003c\u003c \"I do not know what you said\\n\";} // _ is the same as otherwise\n);\n\n```\n\n\nYou can even return values from a match\n\n```cpp\nchar digit = '0';\n\nint value = match(digit,\n\t'0', [](){return 0;},\n\t'1', [](){return 1;},\n\t'2', [](){return 2;},\n\t'3', [](){return 3;},\n\t'4', [](){return 4;},\n\t'5', [](){return 5;},\n// and so on\n);\n```\n\nWe can also do comparisons, and ranges. To do so use `_x` from the `simple_match::placeholders` namespace.\n\n```cpp\nint i = 0;\nmatch(i,\n\t_x \u003c 0, [](int x){std::cout \u003c\u003c x \u003c\u003c \" is a negative number\\n\";},\n\t1 \u003c _x \u003c 10, [](int z){std::cout \u003c\u003c z \u003c\u003c \" is between 1 and 10\\n\"},\n\t_x, [](int x){std::cout \u003c\u003c x \u003c\u003c \" is the value\\n\";}\n);\n\n```\nThere are a some items of interest in the above example. When `_x` is used, it passes its value to the lambda. If `_x` is used without any comparison, it will pass the value to the lambda. Also, because of the way it is overloaded, it is very easy to make ranges using the `\u003c` or `\u003c=` operator as seen in the match above.\n\n### Tuples\n\nNow we can even have more fun! Let's represent a 2d point as a tuple.\n\n```cpp\nstd::tuple\u003cint,int\u003e p(0,0);\n\nmatch(p,\n\tds(0,0), [](){std::cout \u003c\u003c \"Point is at the origin\";},\n\tds(0,_), [](){std::cout \u003c\u003c \"Point is on the horizontal axis\";},\n\tds(_,0), [](){std::cout \u003c\u003c \"Point is on the vertical axis\";}.\n\tds(_x \u003c 10,_), [](int x){std::cout \u003c\u003c x \u003c\u003c \" is less than 10\";},\n\tds(_x,_x), [](int x, int y){ std::cout \u003c\u003c x \u003c\u003c \",\" \u003c\u003c y \u003c\u003c \" Is not on an axis\";}\n);\n```\n\n`ds` stands for de-structure and splits a tuple into its parts. Notice you can use the same expressions as you could without tuples. As before `_x` results in a value being passed to the lambda. `_` matches anything and ignores it, so no corresponding variable is passed to the lambda.\n\nWe can actually use `ds` to deconstruct our own `struct`s and `class`es .\nFirst we have to specialize `simple_match::customization::tuple_adapter` for our type.\n\n```cpp\nstruct point {\n\tint x;\n\tint y;\n\tpoint(int x_,int y_):x(x_),y(y_){}\n};\n\n// Adapting point to be used with ds\nnamespace simple_match {\n\tnamespace customization {\n\t\ttemplate\u003c\u003e\n\t\tstruct tuple_adapter\u003cpoint\u003e{\n\n\t\t\tenum { tuple_len = 2 };\n\n\t\t\ttemplate\u003csize_t I, class T\u003e\n\t\t\tstatic decltype(auto) get(T\u0026\u0026 t) {\n\t\t\t\treturn std::get\u003cI\u003e(std::tie(t.x,t.y));\n\t\t\t}\n\t\t};\n\t}\n}\n```\n\nThen we can use `ds` like we did with a tuple.\n\n```cpp\npoint p{0,0};\n\nmatch(p,\n\tds(0,0), [](){std::cout \u003c\u003c \"Point is at the origin\";},\n\tds(0,_), [](){std::cout \u003c\u003c \"Point is on the horizontal axis\";},\n\tds(_,0), [](){std::cout \u003c\u003c \"Point is on the vertical axis\";}.\n\tds(_x \u003c 10,_), [](int x){std::cout \u003c\u003c x \u003c\u003c \" is less than 10\";},\n\tds(_x,_x), [](int x, int y){ std::cout \u003c\u003c x \u003c\u003c \",\" \u003c\u003c y \u003c\u003c \" Is not on an axis\";}\n);\n```\n\n### Pointers as option types\nSometimes we have pointer that we want to get a value safely out of. To do this we can use `some` and `none` . To do this we have to include `simple_match/some_none.hpp`\n\nLet us use the same `point` as before\n\n```cpp\npoint* pp = new point(0,0);\n\nmatch(pp,\n\tsome(), [](point\u0026 p){std::cout \u003c\u003c p.x \u003c\u003c \" is the x-value\";}\n\tnone(), [](){std::cout \u003c\u003c \"Null pointer\\n\";}\n);\n```\n\nNotice how `some()` converted the pointer to a reference and passed it to us.\n\nNow, that is now how we should allocate memory with a naked new. We would probably use a `std::unique_ptr`. `some` has built in support for `unique_ptr` and `shared_ptr`. So we can write it like this.\n\n```cpp\nauto pp = std::make_unique\u003cpoint\u003e(0,0);\n\nmatch(pp,\n\tsome(), [](point\u0026 p){std::cout \u003c\u003c p.x \u003c\u003c \" is the x-value\";}\n\tnone(), [](){std::cout \u003c\u003c \"Null pointer\\n\";}\n);\n```\nNotice, how our match code did not change.\n\nWe can do better because `some` composes. Since we specialized `tuple_adapter` we can use `ds` with `point`.\n\n```cpp\nauto pp = std::make_unique\u003cpoint\u003e(0,0);\n\nmatch(pp,\n\tsome(ds(0,0)), [](){std::cout \u003c\u003c \"Point is at the origin\";},\n\tsome(ds(0,_)), [](){std::cout \u003c\u003c \"Point is on the horizontal axis\";},\n\tsome(ds(_,0)), [](){std::cout \u003c\u003c \"Point is on the vertical axis\";}.\n\tsome(ds(_x \u003c 10,_)), [](int x){std::cout \u003c\u003c x \u003c\u003c \" is less than 10\";},\n\tsome(ds(_x,_x)), [](int x, int y){ std::cout \u003c\u003c x \u003c\u003c \",\" \u003c\u003c y \u003c\u003c \" Is not on an axis\";},\n\tnone(), [](){std::cout \u003c\u003c \"Null pointer\";}\n);\n```\nNotice how `some` and `ds` compose. If we wanted to to, we could have pointers in tuples, and tuples in pointers and it would just work.\n\n`some` can also use RTTI to do downcasting.\n\nHere is an example. We will now make `point` a base class and have point2d, and point3d as subclasses, and adapt them.\n\n```cpp\nstruct point{\n\tvirtual ~point(){}\n};\n\nstruct point2d:point{\n\tint x;\n\tint y;\n\tpoint2d(int x_,int y_):x(x_),y(y_){}\n};\n\nstruct point3d:point{\n\tint x;\n\tint y;\n\tint z;\n\tpoint3d(int x_,int y_, int z_):x(x_),y(y_),z(z_){}\n};\n\n// Adapting point2d and point3d to be used with ds\nnamespace simple_match {\n\tnamespace customization {\n\t\ttemplate\u003c\u003e\n\t\tstruct tuple_adapter\u003cpoint2d\u003e{\n\n\t\t\tenum { tuple_len = 2 };\n\n\t\t\ttemplate\u003csize_t I, class T\u003e\n\t\t\tstatic decltype(auto) get(T\u0026\u0026 t) {\n\t\t\t\treturn std::get\u003cI\u003e(std::tie(t.x,t.y));\n\t\t\t}\n\t\t};\n\t\ttemplate\u003c\u003e\n\t\tstruct tuple_adapter\u003cpoint3d\u003e{\n\n\t\t\tenum { tuple_len = 3 };\n\n\t\t\ttemplate\u003csize_t I, class T\u003e\n\t\t\tstatic decltype(auto) get(T\u0026\u0026 t) {\n\t\t\t\treturn std::get\u003cI\u003e(std::tie(t.x,t.y,t.z));\n\t\t\t}\n\t\t};\n\t}\n}\n```\n\nThen we can use it like this\n\n```cpp\nstd::unique_ptr\u003cpoint\u003e pp(new point2d(0,0));\n\nmatch(pp,\n\tsome\u003cpoint2d\u003e(ds(_x,_x)), [](int x, int y){std::cout \u003c\u003c x \u003c\u003c \",\" \u003c\u003c y;},\n\tsome\u003cpoint3d\u003e(ds(_x,_x,_x)), [](int x, int y, int z){std::cout \u003c\u003c x \u003c\u003c \",\" \u003c\u003c y \u003c\u003c \",\" \u003c\u003c z;},\n\tsome(), [](point\u0026 p){std::cout \u003c\u003c \"Unknown point type\\n\"},\n\tnone(), [](){std::cout \u003c\u003c \"Null pointer\\n\"}\n);\n\n```\n\nNotice how we can safely downcast, and use `ds` to destructure the `point`. Everything composes nicely.\n\n# Implementation Details\n\nsimple_match actually was easier to implement than I thought it would be. I used the apply sample implementation from http://isocpp.org/files/papers/N3915.pdf to call a function with a tuple as arguments.\n\nHere is the core of the implementation\n\n```cpp\ntemplate\u003cclass T, class U\u003e\nbool match_check(T\u0026\u0026 t, U\u0026\u0026 u) {\n\tusing namespace customization;\n\tusing m = matcher\u003cstd::decay_t\u003cT\u003e, std::decay_t\u003cU\u003e\u003e;\n\treturn m::check(std::forward\u003cT\u003e(t), std::forward\u003cU\u003e(u));\n}\n\n\ntemplate\u003cclass T, class U\u003e\nauto match_get(T\u0026\u0026 t, U\u0026\u0026 u) {\n\tusing namespace customization;\n\tusing m = matcher\u003cstd::decay_t\u003cT\u003e, std::decay_t\u003cU\u003e\u003e;\n\treturn m::get(std::forward\u003cT\u003e(t), std::forward\u003cU\u003e(u));\n}\n\ntemplate\u003cclass T, class A1, class F1\u003e\nauto match(T\u0026\u0026 t, A1\u0026\u0026 a, F1\u0026\u0026 f) {\n\tif (match_check(std::forward\u003cT\u003e(t), std::forward\u003cA1\u003e(a))) {\n\t\treturn detail::apply(f, match_get(std::forward\u003cT\u003e(t), std::forward\u003cA1\u003e(a)));\n\t}\n\telse {\n\t\tthrow std::logic_error(\"No match\");\n\t}\n}\n\n\ntemplate\u003cclass T, class A1, class F1, class A2, class F2, class... Args\u003e\nauto match(T\u0026\u0026 t, A1\u0026\u0026 a, F1\u0026\u0026 f, A2\u0026\u0026 a2, F2\u0026\u0026 f2, Args\u0026\u0026... args) {\n\tif (match_check(t, a)) {\n\t\treturn detail::apply(f, match_get(std::forward\u003cT\u003e(t), std::forward\u003cA1\u003e(a)));\n\t}\n\telse {\n\t\treturn match(t, std::forward\u003cA2\u003e(a2), std::forward\u003cF2\u003e(f2), std::forward\u003cArgs\u003e(args)...);\n\t}\n}\n```\n\n`match` is a variadic function that takes the value to be matched, and then parameters for the match criteria, and lambda to be executed if the criteria succeeds. It goes through calling `match_check` until it returns true. Then it calls `match_get` to get a tuple of the values that need to be forwarded to the lambda, and uses apply to call the lambda.\n\nThe match types are implemented by specializing `simple_match::customization::matcher`\n```cpp\nnamespace customization {\n\ttemplate\u003cclass T, class U\u003e\n\tstruct matcher;\n}\n```\n\nFor an example, here is how the matcher that matches to values is implemented. Note, that it does not pass any values to the lambda and so returns an empty tuple.\n\n```cpp\n// Match same type\ntemplate\u003cclass T\u003e\nstruct matcher\u003cT, T\u003e {\n\tstatic bool check(const T\u0026 t, const T\u0026 v) {\n\t\treturn t == v;\n\t}\n\tstatic auto get(const T\u0026, const T\u0026) {\n\t\treturn std::tie();\n\t}\n}\n```\n\nI hope you enjoy using this code, as much as I have enjoyed writing it. Please give me any feedback you may have.\n\n-- John R. Bandela, MD\n\n\u003e Written with [StackEdit](https://stackedit.io/).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjbandela%2Fsimple_match","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjbandela%2Fsimple_match","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjbandela%2Fsimple_match/lists"}