Pattern Matching in C++14.
MPark.Patterns is an experimental pattern matching library for C++14.
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 <functional>
#include <iostream>
#include <sstream>
#include <mpark/match.hpp>
int eval(const std::string& equation) {
std::istringstream strm(equation);
strm.exceptions(std::istringstream::failbit);
int lhs, rhs;
std::string op;
strm >> lhs >> op >> rhs;
using namespace mpark;
return match(lhs, op, rhs)(
pattern(arg, "plus", arg) = std::plus<>{},
pattern(arg, "minus", arg) = std::minus<>{},
pattern(arg, "mult", arg) = std::multiplies<>{},
pattern(arg, "div", arg) = std::divides<>{});
}
int main() {
std::cout << eval("101 plus 202") << '\n'; // prints "303".
std::cout << eval("64 div 2") << '\n'; // prints "32".
}
using namespace mpark;
match(<expr>...)(
pattern(<pattern>...) = <handler>,
pattern(<pattern>...) = <handler>,
// ...
);
A wildcard pattern matches and ignores any value.
None.
_
(underscore)
int factorial(int n) {
using namespace mpark;
return match(n)(pattern(0) = [] { return 1; },
pattern(_) = [n] { return n * factorial(n - 1); });
}
A bind pattern passes any value matched by <pattern>
to the handler.
None.
arg(<pattern>)
arg
-- alias forarg(_)
int factorial(int n) {
using namespace mpark;
return match(n)(pattern(0) = [] { return 1; },
pattern(arg) = [](auto n) { return n * factorial(n - 1); });
}
A product pattern matches values that holds multiple values.
The type T
satisfies Product
if given a variable x
of type T
,
- If
std::tuple_size<T>
is a complete type,x.get<I>()
is valid for allI
in[0, std::tuple_size<T>::value)
. Otherwise,get<I>(x)
is valid for allI
in[0, std::tuple_size<T>::value)
. std::tuple_size<T>::value
is a well-formed integer constant expression.
NOTE: These requirements are very similar to the requirements for C++17 Structured Bindings.
prod(<pattern>...)
auto t = std::make_tuple(101, "hello", 1.1);
// C++17 Structured Bindings:
const auto& [x, y, z] = t;
// ...
// C++14 MPark.Patterns:
using namespace mpark;
match(t)(
pattern(prod(arg, arg, arg)) = [](const auto& x, const auto& y, const auto& z) {
// ...
});
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;
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 << std::endl; });
}
}
A sum pattern matches values that holds one of a set of alternatives.
The sum<T>
pattern matches if the given value holds an instance of T
.
The sum
pattern matches values of a sum type,
The type T
satisfies Sum<U>
if given a variable x
of type T
,
- If
mpark::variant_size<T>
is a complete type,x.get<U>()
is valid. Otherwise,get<U>(x)
is valid. mpark::variant_size<T>::value
is a well-formed integer constant expression.
The type T
satisfies Sum
if given a variable x
of type T
,
- If
mpark::variant_size<T>
is a complete type,visit([](auto&&) {}, x)
is valid. mpark::variant_size<T>::value
is a well-formed integer constant expression.
sum<U>(<pattern>)
sum(<pattern>)
using str = std::string;
mpark::variant<int, str> v = 42;
using namespace mpark;
match(v)(pattern(sum<int>(_)) = [] { std::cout << "int\n"; },
pattern(sum<str>(_)) = [] { std::cout << "str\n"; });
// prints "int".
using str = std::string;
mpark::variant<int, str> v = "hello world!";
struct {
void operator()(int n) const { std::cout << "int: " << n << '\n'; }
void operator()(const str& s) const { std::cout << "str: " << s << '\n'; }
} handler;
using namespace mpark;
match(v)(pattern(sum(arg)) = handler);
// prints: "str: hello world!".
An optional pattern matches values that can be dereferenced, and tested as a bool
.
The type T
satisfies Optional
if given a variable x
of type T
,
*x
is a valid expression.x
is contextually convertible tobool
.
some(<pattern>)
none
int *p = nullptr;
using namespace mpark;
match(p)(pattern(some(_)) = [] { std::cout << "some\n"; },
pattern(none) = [] { std::cout << "none\n"; });
// prints "none".
boost::optional<int> o = 42;
using namespace mpark;
match(o)(
pattern(some(arg)) = [](auto x) { std::cout << "some(" << x << ")\n"; },
pattern(none) = [] { std::cout << "none\n"; });
// prints "some(42)".
A variadic pattern matches 0 or more values that match a given pattern.
None.
variadic(<pattern>)
auto x = std::make_tuple(101, "hello", 1.1);
using namespace mpark;
match(x)(
pattern(prod(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 <typename F, typename Tuple>
decltype(auto) apply(F &&f, Tuple &&t) {
using namespace mpark;
return match(std::forward<T>(t))(
pattern(prod(variadic(arg))) = std::forward<F>(f));
}
and even C++17 std::visit
:
template <typename F, typename... Vs>
decltype(auto) visit(F &&f, Vs &&... vs) {
using namespace mpark;
return match(std::forward<Vs>(vs)...)(
pattern(variadic(sum(arg))) = std::forward<F>(f));
}
We can even get a little fancier:
int x = 42;
auto y = std::make_tuple(101, "hello", 1.1);
using namespace mpark;
match(x, y)(
pattern(arg, prod(variadic(arg))) = [](const auto&... xs) {
int dummy[] = { (std::cout << xs << ' ', 0)... };
(void)dummy;
});
// prints: "42 101 hello 1.1 "
An alternation pattern matches values that match any of the given patterns.
None.
anyof(<pattern>...)
std::string s = "large";
using namespace mpark;
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"; });
While pattern matching is used to match values against patterns and bind the desired portions, pattern guards are used to test whether the bound values satisfy some predicate.
when(<condition>);
using namespace mpark;
match(101, 202)(
pattern(arg, arg) = [](auto&& lhs, auto&& rhs) { when(lhs > rhs); std::cout << "GT\n"; },
pattern(arg, arg) = [](auto&& lhs, auto&& rhs) { when(lhs < rhs); std::cout << "LT\n"; },
pattern(arg, arg) = [](auto&& lhs, auto&& rhs) { when(lhs == rhs); std::cout << "EQ\n"; });
// prints "LT".