patterns

by mpark

mpark /patterns

This is an experimental library that has evolved to P1371, proposed for C++23.

437 Stars 24 Forks Last release: about 3 years ago (v0.3.0) Boost Software License 1.0 125 Commits 3 Releases

Available items

No Items, yet!

The developer of this repository has not created any items for sale yet. Need a bug fixed? Help with integration? A different license? Create a request here:

MPark.Patterns

This is an experimental library that has evolved to P1371, being proposed for C++23.

release travis license godbolt wandbox

Introduction

MPark.Patterns is an experimental pattern matching library for C++17.

It determines whether a given value matches a pattern and, if it does, binds the desired portions of the value to a handler.

Pattern matching has been introduced to many programming languages outside of the functional world, and this library draws inspiration from languages such as Haskell, OCaml, Rust, Scala, and Swift.

#include 

#include

void fizzbuzz() { using namespace mpark::patterns; for (int i = 1; i <= 100; ++i) { match(i % 3, i % 5)( pattern(0, 0) = [] { std::printf("fizzbuzz\n"); }, pattern(0, ) = [] { std::printf("fizz\n"); }, pattern(, 0) = [] { std::printf("buzz\n"); }, pattern(_, _) = [i] { std::printf("%d\n", i); }); } }

int main() { fizzbuzz(); }

Basic Syntax

using namespace mpark::patterns;
IDENTIFIERS(...);  // optional
match(...)(
    pattern(...) = [](...) { /* ... */ },
    pattern(...) = [](...) { /* ... */ },
    // ...
);

Types of Patterns

Expression Pattern

An expression pattern matches if the value compares equal to the given expression.

Requirements

Given an expression

e
and a value
x
,
e == x
must be a valid expression.

Syntax

Examples

int factorial(int n) {
  using namespace mpark::patterns;
  return match(n)(pattern(0) = [] { return 1; },
                  //      ^ expression
                  pattern(_) = [n] { return n * factorial(n - 1); });
}

Arg Pattern

An arg pattern matches any value, and binds the value by passing it to the corresponding handler. This pattern can be repeated in a single pattern, and they match independent of each other.

Requirements

None.

Syntax

  • arg
  • arg()

Examples

int factorial(int n) {
  using namespace mpark::patterns;
  return match(n)(pattern(0) = [] { return 1; },
                  pattern(arg) = [](auto n) { return n * factorial(n - 1); });
                  //      ^^^ arg
}
using P = std::pair;
P p = {101, 202};

using namespace mpark::patterns; match(p)(pattern(arg(ds(101, ))) = [](P &p) { std::cout << "pair!\n"; }, // ^^^ pass the pair! pattern() = [] { std::cout << "not a pair\n"; }); // prints: "pair!"

Wildcard Pattern

A wildcard pattern matches any value and discards the value by not passing it to the corresponding handler. This pattern can be repeated within a single pattern, and they match independent of each other.

Requirements

None.

Syntax

  • _
    (underscore)

Examples

int factorial(int n) {
  using namespace mpark::patterns;
  return match(n)(pattern(0) = [] { return 1; },
                  pattern(_) = [n] { return n * factorial(n - 1); });
                  //      ^ wildcard
}

Identifier Pattern

An identifier pattern is similar to

arg
and
_
in that it matches any value.

Two major differences are: 1. Ability to operate on names rather than just

arg
or
_
. 2. When an identifier is repeated within a single pattern, the values bound to the repeated identifiers must compare equal for the pattern to match.

There are two types of identifier patterns: binding and discarding.

Requirements

If no identifiers are repeated, none.

Otherwise, let

x
be a repeated identifer and
v, vs...
be the group of values matched by the repeated instances of
x
. Then
(... && (v == vs))
must be a valid expression. The same requirement applies to any other repeated identifiers in a single pattern.

Syntax

  • IDENTIFIERS(...);
  • ()

If

 has a leading 
_
(underscore), it is a discarding identifier. Otherwise, it is a binding identifier.

Examples

void is_same(int lhs, int rhs) {
  using namespace mpark::patterns;
  IDENTIFIERS(x, y);
  match(lhs, rhs)(
      pattern(x, x) = [](auto) { std::cout << "same\n"; },
      //      ^  ^ binding identifier (repeated)
      pattern(x, y) = [](auto, auto) { std::cout << "diff\n"; }
      //      ^  ^ binding identifier
  );
}

is_same(101, 101); // prints: "same" is_same(101, 202); // prints: "diff"

void is_same(int lhs, rhs) {
  using namespace mpark::patterns;
  IDENTIFIERS(_x, _y);
  match(42, 42)(
      pattern(_x, _x) = [] { std::cout << "same\n"; },
      //      ^   ^ discarding identifier (repeated)
      pattern(_x, _y) = [] { std::cout << "diff\n"; }
      //      ^   ^ discarding identifier
  );
}

is_same(101, 101); // prints: "same" is_same(101, 202); // prints: "diff"

std::tuple t = {101, 202};
std::optional o = 101;

using namespace mpark::patterns; IDENTIFIERS(x, y); match(t, o)( pattern(ds(x, x), some(x)) = [](auto x) { std::cout << "They're all " << x << "!\n"; }, pattern(ds(x, y), some(x)) = [](auto x, auto y) { std::cout << "I recognize x-y-x!\n"; }, pattern(_) = [] { std::cout << "I don't know.\n"; }); // prints: "I recognize x-y-x!"

Destructure Pattern

A destructure pattern matches values that hold multiple values.

Requirements

Given a value

x
of type
T
,
T
must satisfy one of the following conditions: 1.
std::is_array_v == true
, or 2.
std::is_class_v == true
and
std::tuple_size
is a complete type, or 3.
std::is_aggregate_v == true

If (2), the following conditions must also be satisfied. -

std::tuple_size::value
must be a well-formed integer constant expression, and -
x.get()
or
get(x)
must be a valid expression for all
I
in
[0, std::tuple_size::value)

For aggregate types (3), the current implementation only supports types that contain upto 16 non-static data members.

NOTE: These requirements are very similar to the requirements for C++17 Structured Binding.

Syntax

  • ds(...)

Examples

auto t = std::make_tuple(101, "hello", 1.1);

// C++17 Structured Binding: const auto & [x, y, z] = t; // ...

// MPark.Patterns: using namespace mpark::patterns; IDENTIFIERS(x, y, z); match(t)( pattern(ds(x, y, z)) = [](auto &&x, auto &&y, auto &&z) { /* ... */ }); // ^^^^^^^^^^^ destructure

NOTE: The top-level is wrapped by a

tuple
, allowing us to write:
void fizzbuzz() {
  for (int i = 1; i <= 100; ++i) {
    using namespace mpark::patterns;
    match(i % 3, i % 5)(
        pattern(0, 0) = [] { std::cout << "fizzbuzz\n"; },
        pattern(0, _) = [] { std::cout << "fizz\n"; },
        pattern(_, 0) = [] { std::cout << "buzz\n"; },
        pattern(_, _) = [i] { std::cout << i << '\n'; });
  }
}

Optional Pattern

An optional pattern matches values that can be dereferenced, and tested as a

bool
.

Requirements

Given a value

x
of type
T
,
T
must satisfy all of the following conditions: -
*x
is a valid expression, and -
x
is contextually convertible to
bool

Syntax

  • some()
  • none

Examples

int *p = nullptr;

using namespace mpark::patterns; match(p)(pattern(some(_)) = [] { std::cout << "some\n"; }, pattern(none) = [] { std::cout << "none\n"; }); // prints: "none"

std::optional o = 42;

using namespace mpark::patterns; match(o)( pattern(some(arg)) = [](auto x) { std::cout << "some(" << x << ")\n"; }, pattern(none) = [] { std::cout << "none\n"; }); // prints: "some(42)"

As Pattern

An as pattern matches values that is capable of holding a value of multiple types.

Requirements

Given a value

x
of type
T
, and a pattern
as()
,
T
must satisfy one of the following conditions: 1.
std::is_polymorphic_v == true
, or 2.
std::is_class_v == true
and
std::variant_size
is a complete type, or 3.
x.any_cast()
or
any_cast(&x)
is a valid expression
  • If (1),
    dynamic_cast(&x)
    must be a valid expression, where
    U'
    is pointer to
    U
    with the same cv-qualifiers as
    decltype(x)
  • If (2),
    x.get_if()
    or
    get_if(&x)
    must be a valid expression

Syntax

  • as()

Examples

struct Shape { virtual ~Shape() = default; };
struct Circle : Shape {};
struct Square : Shape {};

std::unique_ptr shape = std::make_unique();

using namespace mpark::patterns; match(shape)(pattern(some(as())) = [] { std::cout << "Circle\n"; }, pattern(some(as())) = [] { std::cout << "Square\n"; }); // prints: "Square"

using str = std::string;
std::variant v = 42;

using namespace mpark::patterns; match(v)(pattern(as()) = [] { std::cout << "int\n"; }, pattern(as()) = [] { std::cout << "str\n"; }); // prints: "int"

using str = std::string;
std::any a = str("hello");

using namespace mpark::patterns; match(a)(pattern(as()) = [] { std::cout << "int\n"; }, pattern(as()) = [] { std::cout << "str\n"; }); // prints: "str"

Visit Pattern

A visit pattern matches values that is capable of holding a value of closed set of types, and can be visited.

Requirements

Given a value

x
, The following code must be valid.
using std::visit;
visit([](auto &&) {}, x);`

Syntax

  • vis()

Examples

using str = std::string;
std::variant v = "hello world!";

struct Visitor { void operator()(int n) const { std::cout << "int: " << n << '\n'; } void operator()(const str &s) const { std::cout << "str: " << s << '\n'; } };

using namespace mpark::patterns; match(v)(pattern(vis(arg)) = Visitor{}); // prints: "str: hello world!".

Alternation Pattern

An alternation pattern matches values that match any of the given patterns.

Requirements

None.

Syntax

  • anyof(...)

Examples

std::string s = "large";

using namespace mpark::patterns; match(s)( pattern(anyof("big", "large", "huge")) = [] { std::cout << "big!\n"; }, pattern(anyof("little", "small", "tiny")) = [] { std::cout << "small.\n"; }, pattern(_) = [] { std::cout << "unknown size.\n"; });

Variadic Pattern

A variadic pattern matches 0 or more values that match a given pattern.

Requirements

  • A variadic pattern can only appear within a destructure pattern.
  • A variadic pattern can only appear once.

Syntax

  • variadic()

Examples

auto x = std::make_tuple(101, "hello", 1.1);

using namespace mpark::patterns; match(x)( pattern(ds(variadic(arg))) = [](const auto &... xs) { int dummy[] = { (std::cout << xs << ' ', 0)... }; (void)dummy; }); // prints: "101 hello 1.1 "

This could also be used to implement C++17

std::apply
:

template 
decltype(auto) apply(F &&f, Tuple &&t) {
  using namespace mpark::patterns;
  return match(std::forward(t))(
      pattern(ds(variadic(arg))) = std::forward(f));
}

and even C++17

std::visit
:

template 
decltype(auto) visit(F &&f, Vs &&... vs) {
  using namespace mpark::patterns;
  return match(std::forward(vs)...)(
      pattern(variadic(vis(arg))) = std::forward(f));
}

We can even get a little fancier:

int x = 42;
auto y = std::make_tuple(101, "hello", 1.1);

using namespace mpark::patterns; match(x, y)( pattern(arg, ds(variadic(arg))) = [](const auto &... xs) { int dummy[] = { (std::cout << xs << ' ', 0)... }; (void)dummy; }); // prints: "42 101 hello 1.1 "

Pattern Guards

While pattern matching is very useful to match values against patterns, it mainly focuses on equality comparisons. Pattern guards are used to test whether the bound values satisfy some predicate.

WHEN

A

WHEN
clause appears inside the handler as the only expression. A
WHEN
clause differs from an
if
statement in that if the conditional expression evaluates to
false
, the
WHEN
clause falls through to the next case, whereas the
if
statement would stay in the handler.

Requirements

The

WHEN
clause may only appear once inside the handler as the only expression, and the conditional expression must be contextually convertible to
bool
.

Syntax

  • WHEN() { /* ... */ };

Examples

using namespace mpark::patterns;
IDENTIFIERS(x, y);
match(101, 202)(
    pattern(x, x) = [](int) { std::cout << "EQ\n"; },
    pattern(x, y) = [](int x, int y) { WHEN(x > y) { std::cout << "GT\n"; }; },
    pattern(x, y) = [](int x, int y) { WHEN(x < y) { std::cout << "LT\n"; }; });
// prints: "LT"

Pattern Statements

if_let

This construct is simply syntactic sugar for the following situation:

std::optional o = 42;

using namespace mpark::patterns; match(o)( pattern(some(arg)) = [](auto x) { ... }, pattern(_) = [] {});

For this situation,

if_let
provides a cleaner interface:
std::optional o = 42;

using namespace mpark::patterns; if_let (pattern(some(arg)) = o) = [](auto x) { // ... };

for_let

Suppose we want to match each element in a range-based

for
loop with pattern. We could imagine being able to write something like:
std::vector<:pair int>> pairs = {{0, 1}, {1, 1}, {0, 0}, {0, 2}};

for ((0, x) : pairs) { std::cout << x << ' '; } // prints: "1 0 2 " </:pair>

With

if_let
we can say something close-ish:
std::vector<:pair int>> pairs = {{0, 1}, {1, 1}, {0, 0}, {0, 2}};

for (const auto &p : pairs) { using namespace mpark::patterns; if_let (pattern(ds(0, arg)) = p) = [](auto x) { std::cout << x << ' '; }; } // prints: "1 0 2 " </:pair>

for_let
allows the above example to be written more concisely:
std::vector<:pair int>> pairs = {{0, 1}, {1, 1}, {0, 0}, {0, 2}};

using namespace mpark::patterns; for_let (pattern(ds(0, arg)) = pairs) = [](auto x) { std::cout << x << ' '; }; // prints: "1 0 2 " </:pair>

for_let
also provides the
break
and
continue
statements with
return Break;
and
return Continue;
respectively.
std::vector<:pair int>> pairs = {{0, 1}, {1, 1}, {0, 0}, {0, 2}};

using namespace mpark::patterns; for_let (pattern(ds(0, arg)) = pairs) = [](auto x) { if (x == 1) return Break; std::cout << x << ' '; return Continue; }; // prints: "0 " </:pair>

std::vector<:pair int>> pairs = {{0, 1}, {1, 1}, {0, 0}, {0, 2}};

using namespace mpark::patterns; for_let (pattern(ds(0, arg)) = pairs) = [](auto x) { if (x == 0) return Continue; std::cout << x << ' '; return Continue; }; // prints: "1 2 " </:pair>

Requirements

This library requires a standard conformant C++17 compiler. The following compilers are continously tested:

| Compiler | Operating System | Version String | |-------------|--------------------|------------------------------------------------------------------------------| | GCC 7.4.0 | Ubuntu 16.04.6 LTS | g++-7 (Ubuntu 7.4.0-1ubuntu1~16.04~ppa1) 7.4.0 | | GCC 8.1.0 | Ubuntu 16.04.6 LTS | g++-8 (Ubuntu 8.1.0-5ubuntu1~16.04) 8.1.0 | | Clang 5.0.0 | Ubuntu 16.04.6 LTS | clang version 5.0.0-3~16.04.1 (tags/RELEASE500/final) | | Clang 6.0.0 | Ubuntu 16.04.6 LTS | clang version 6.0.0-1ubuntu2~16.04.1 (tags/RELEASE600/final) | | Clang 7.1.0 | Ubuntu 16.04.6 LTS | clang version 7.1.0-svn353565-1~exp1~20190408084827.60 (branches/release_70) |

CMake Variables

  • MPARK_PATTERNS_INCLUDE_TESTS
    :

    BOOL
    (default:
    OFF
    )

    Build tests.

Unit Tests

Refer to test/README.md.

License

Distributed under the Boost Software License, Version 1.0.

Related Work

We use cookies. If you continue to browse the site, you agree to the use of cookies. For more information on our use of cookies please see our Privacy Policy.