容易开始。
单头库。
无宏的API。
没有堆内存分配。
可移植性:使用GCC/Clang/MSVC在Ubuntu,MacOS和Windows下进行连续测试。
没有外部依赖性。
可靠性:严格的编译器检查选项 +消毒剂 + Valgrind。
合并模式。
可扩展的用户可以通过组成现有的模式来定义自己的模式,或者创建全新的模式。
支持破坏借助元组和类似射程的容器。
部分支持恒定表达。
matchit.h只需下载标题文件matchit.h ,然后将其放入依赖项目录中。
就是这样。
您可以通过此bash命令下载
wget https://raw.githubusercontent.com/BowenFu/matchit.cpp/main/include/matchit.h在您的cmakelists.txt中包括代码片段:
include (FetchContent)
FetchContent_Declare(
matchit
GIT_REPOSITORY https://github.com/BowenFu/matchit.cpp.git
GIT_TAG main)
FetchContent_GetProperties(matchit)
if ( NOT matchit_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} " )并添加${matchit_SOURCE_DIR}/include insubly路径。
用最新版本的标签替换main ,以避免API兼容性破坏。
克隆仓库通过
git clone --depth 1 https://github.com/BowenFu/matchit.cpp
通过
cd matchit.cpp
cmake -B ./build
cd build
make install
然后在您的cmakelists.txt中使用find_package。
(感谢 @daljit97添加了支持。)
vcpkg install matchit
现在,图书馆已提交给柯南中心指数。
您现在可以通过柯南安装图书馆。
(感谢@sanblch添加了支持。)
为了使调试更容易,请尝试以单独的行编写Lambda功能主体,以便您可以在其中设置断点。
pattern | xyz = [&]
{
// Separate lines for function body <- set break points here
}与调试更友好相比
pattern | xyz = [&] { /* some codes here */ }, // <- Set break points here, you will debug into the library.除非您真正决定在此库中根本决定根本原因 /修复一些错误,否则请勿在此库中进行调试,就像您不会调试到STL变体或范围一样。
请尝试创建一个最少的样本来重现您遇到的问题。您可以以这种方式更快地引起问题。
您还可以在此存储库中创建一个问题,并附加最小的示例代码,我会尽快响应(有时请期望延迟一两天)。
有关语法设计详细信息,请参考参考。
Rust到IT(IT)的文档提供了相应的生锈样品的等效样本。
在那里,您可能会有一张图片,即match(it)编码是什么。
模式匹配提案的文档匹配(IT)提供了匹配模式匹配建议中相应样本的等效样本。
在那里,您会看到图书馆的利弊,而不是该提案。
让我们在图书馆开始旅程!
(有关完整的样本,请参阅样本目录。)
以下示例显示了如何使用match(it)库实现阶乘。
# include " matchit.h "
constexpr int32_t factorial ( int32_t n)
{
using namespace matchit ;
assert (n >= 0 );
return match (n)(
pattern | 0 = 1 ,
pattern | _ = [n] { return n * factorial (n - 1 ); }
);
}模式匹配的基本语法是
match (VALUE)
(
pattern | PATTERN1 = HANDLER1,
pattern | PATTERN2 = HANDLER2,
...
)这是一个函数调用,将返回处理程序返回的一些值。返回类型是所有处理程序的常见类型。如果所有处理程序都不返回值,则返回类型将是无效的。来自多个处理程序的不兼容返回类型是编译错误。当操作员返回值时,模式必须详尽。如果所有模式都不匹配,将会发生运行时错误。如果处理人员的返回类型都是无效的,则不是错误。
处理程序也可以是值或ID变量。 1等同于[]{return 1;} 。
通配符_将匹配任何值。始终将其用作最后一个模式是一种常见的做法,在我们的库中扮演与switch default case相同的角色,以避免案例逃脱。
我们可以同时匹配多个值:
# include " matchit.h "
constexpr int32_t gcd ( int32_t a, int32_t b)
{
using namespace matchit ;
return match (a, b)(
pattern | ds (_, 0 ) = [&] { return a >= 0 ? a : -a; },
pattern | _ = [&] { return gcd (b, a%b); }
);
}
static_assert (gcd( 12 , 6 ) == 6);请注意,某些模式支持ConstexPR匹配,即您可以在编译时匹配它们。从上面的代码段中,我们可以看到gcd(12, 6)可以在编译时间内执行。
与其他编程语言中的匹配模式不同,变量可以正常使用match(it)中的模式,这在以下示例中显示:
# include " matchit.h "
# include < map >
template < typename Map, typename Key>
constexpr bool contains (Map const & map, Key const & key)
{
using namespace matchit ;
return match (map. find (key))(
pattern | map. end () = false ,
pattern | _ = true
);
}我们可以使用谓词模式对要匹配的值进行一些限制。
constexpr double relu ( double value)
{
return match (value)(
pattern | (_ >= 0 ) = value,
pattern | _ = 0
);
}
static_assert (relu( 5 ) == 5);
static_assert (relu(- 5 ) == 0);我们超载了一些通配符符号的操作员_ ,以促进基本谓词的使用。
有时我们想分享一个用于多种模式的处理程序,或者是拯救的模式:
# include " matchit.h "
constexpr bool isValid ( int32_t n)
{
using namespace matchit ;
return match (n)(
pattern | or_ ( 1 , 3 , 5 ) = true ,
pattern | _ = false
);
}
static_assert (isValid( 5 ));
static_assert (!isValid( 6 ));模式用于结合多个谓词模式。
当您想从主题中提取一些信息时,应用程序模式很强大。它的语法是
app (PROJECTION, PATTERN)一个简单的样本来检查数字是否大可能是:
# include " matchit.h "
constexpr bool isLarge ( double value)
{
using namespace matchit ;
return match (value)(
pattern | app (_ * _, _ > 1000 ) = true ,
pattern | _ = false
);
}
// app with projection returning scalar types is supported by constexpr match.
static_assert (isLarge( 100 ));请注意, _ * _生成一个计算输入平方的函数对象,可以被视为[](auto&& x){ return x*x;}的简短版本。
如果我们已经提取了值,我们可以绑定值吗?当然,标识符模式适合您。
让我们记录正方形结果,并使用标识符模式该代码为
# include < iostream >
# include " matchit.h "
bool checkAndlogLarge ( double value)
{
using namespace matchit ;
Id< double > s;
return match (value)(
pattern | app (_ * _, s. at (_ > 1000 )) = [&] {
std::cout << value << " ^2 = " << *s << " > 1000! " << std::endl;
return true ; },
pattern | _ = false
);
}要使用标识符模式,我们需要先定义/声明标识符( Id<double> s ) 。 (不要将其标记为常量。)如果您使用其他编程语言的标识符模式,这可能会有些奇怪。这是由于语言限制。但是不要难过。这增加了详细的详细性使我们有可能在模式内使用变量。您可能永远无法使用其他编程语言来执行此操作。
这里*运算符用于取消标识符中的值。要注意的一件事是,标识符仅在match范围内有效。不要试图在外面解雇它。
Id::at类似于Rust中的@模式,即当匹配子量时,绑定值。
另请注意,当相同的标识符多次绑定时,绑定值必须通过operator==彼此相等。样本以检查阵列是否对称:
# include " matchit.h "
constexpr bool symmetric (std::array< int32_t , 5 > const & arr)
{
using namespace matchit ;
Id< int32_t > i, j;
return match (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);现在,我们来到了最强大的部分:破坏性模式。破坏性模式可用于std::tuple , std::pair , std::array (固定大小的容器)和动态容器或大小范围( std::vector , std::list , std::set等),带有std::begin :: begin std::end Supports。
可以省略最外面的ds内部模式。当模式接收多个参数时,它们被视为DS模式的副本。
# include " matchit.h "
template < typename T1, typename T2>
constexpr auto eval (std::tuple< char , T1, T2> const & expr)
{
using namespace matchit ;
Id<T1> i;
Id<T2> j;
return match (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 ;
});
}一些运算符已被超载Id ,因此i + j将返回一个无效函数,该函数返回*i + *j的值。
还有一些方法可以破坏您的结构 /类别,使您的结构 /类元组样或采用应用程序模式。第二个选项看起来像
// Another option to destructure your struct / class.
constexpr auto dsByMember (DummyStruct const &v)
{
using namespace matchit ;
// compose patterns for destructuring struct DummyStruct.
constexpr auto dsA = dsVia (&DummyStruct::size, &DummyStruct::name);
Id< char const *> name;
return match (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 " });让我们继续旅程。有时您有多个标识符,并且希望对它们的关系施加限制。有可能吗?当然!比赛后卫来了。它的语法是
pattern | PATTERN | when(GUARD) = HANDLER说,我们只想在两个标识符等于某些值的总和时匹配,我们可以将代码写为
# include < array >
# include " matchit.h "
constexpr bool sumIs (std::array< int32_t , 2 > const & arr, int32_t s)
{
using namespace matchit ;
Id< int32_t > i, j;
return match (arr)(
pattern | ds (i, j) | when (i + j == s) = true ,
pattern | _ = false
);
}
static_assert (sumIs(std::array< int32_t , 2 >{ 5 , 6 }, 11 ));那很酷,不是吗?请注意, i + j == s将返回一个无效函数,该功能返回*i + *j == s的结果。
现在我们来了OOO模式。那是什么?你可能会问。在某些编程语言中,它称为REST模式。您可以将任意数量的项目与之匹配。不过,它只能在ds模式中使用,最多只能在ds模式中出现一个OOO模式。当您想检查元组模式时,您可以像以下那样写代码。
# include < array >
# include " matchit.h "
template < typename Tuple>
constexpr int32_t detectTuplePattern (Tuple const & tuple)
{
using namespace matchit ;
return match (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);更重要的是,在破坏std::array或其他容器 /范围时,我们可以将子范围绑定到OOO模式。那很酷。我们可以检查AN/A array/vector/list/set/map/subrange/...与:
template < typename Range>
constexpr bool recursiveSymmetric (Range const &range)
{
Id< int32_t > i;
Id<SubrangeT<Range const >> subrange;
return match (range)(
pattern | ds (i, subrange. at (ooo), i) = [&] { return recursiveSymmetric (*subrange); },
pattern | ds (_, ooo, _) = false ,
pattern | _ = true
);在第一个模式中,我们要求头等于末端。如果是这种情况,我们会通过递归调用进一步检查其余部分(绑定到子量)。一旦一些嵌套的呼叫无法满足该要求(落到第二种模式)后,检查就会失败。否则,只有一个元素或范围大小为零时,最后的模式就会匹配,我们将返回true。
我们已经完成了核心模式。现在,让我们开始编写模式的旅程。
如果您在Rust中使用了模式匹配功能,则应该熟悉一些模式,而无需模式。
一些 /无模式可用于匹配原始指针, std::optional , std::unique_ptr , std::shared_ptr和其他可以转换为bool并进行验证的类型。典型的样本可以是
# include " matchit.h "
template < typename T>
constexpr auto square (std::optional<T> const & t)
{
using namespace matchit ;
Id<T> id;
return match (t)(
pattern | some (id) = id * id,
pattern | none = 0
);
}
constexpr auto x = std::make_optional( 5 );
static_assert (square(x) == 25);一些模式接受了一个副本。在样品中,子图案是一个标识符,我们将剥离的结果结合到它。没有模式一个人。
有些和没有模式不是match(it)中的原子模式,它们是通过
template < typename T>
constexpr auto cast = []( auto && input) {
return static_cast <T>(input);
};
constexpr auto deref = []( auto &&x) { return *x; };
constexpr auto some = []( auto const pat) {
return and_ ( app (cast< bool >, true ), app (deref, pat));
};
constexpr auto none = app(cast< bool >, false );对于某种模式,首先,我们将值投放为布尔值,如果布尔值为真,我们可以进一步取消它。否则,比赛失败。对于无模式,我们简单地检查转换的布尔值是否为false。
由于模式对于处理sum type非常有用,包括类层次结构, std::variant和std::any 。 std::variant和std::any可以访问的
# include " matchit.h "
template < typename T>
constexpr auto getClassName (T const & v)
{
using namespace matchit ;
return match (v)(
pattern | as< char const *>(_) = " chars " ,
pattern | as< int32_t >(_) = " int32_t "
);
}
constexpr std::variant< int32_t , char const *> v = 123 ;
static_assert (getClassName(v) == std::string_view{ " int32_t " });类层次结构可以匹配为
struct Shape
{
virtual ~Shape () = default ;
};
struct Circle : Shape {};
struct Square : Shape {};
auto getClassName (Shape const &s)
{
return match (s)(
pattern | as<Circle>(_) = " Circle " ,
pattern | as<Square>(_) = " Square "
);
}由于模式也不是原子模式。它是通过
template < typename T>
constexpr AsPointer<T> asPointer;
template < typename T>
constexpr auto as = []( auto const pat) {
return app (asPointer<T>, some (pat));
};对于类, dynamic_cast默认使用AS模式,但是我们可以通过自定义点更改行为。用户可以通过定义其类的get_if函数来自定义下铸件,类似于std::get_if for std::variant :
# include < iostream >
# include " matchit.h "
enum class Kind { kONE , kTWO };
class Num
{
public:
virtual ~Num () = default ;
virtual Kind kind () const = 0;
};
class One : public Num
{
public:
constexpr static auto k = Kind:: kONE ;
Kind kind () const override { return k; }
};
class Two : public Num
{
public:
constexpr static auto k = Kind:: kTWO ;
Kind kind () const override
{
return k;
}
};
template <Kind k>
constexpr auto kind = app(&Num::kind, k);
template < typename T>
auto get_if (Num const * num) {
return static_cast <T const *>(num-> kind () == T::k ? num : nullptr );
}
int32_t staticCastAs (Num const & input)
{
using namespace matchit ;
return match (input)(
pattern | as<One>(_) = 1 ,
pattern | kind<Kind:: kTWO > = 2 ,
pattern | _ = 3
);
}
int32_t main ()
{
std::cout << staticCastAs (One{}) << std::endl;
return 0 ;
}还有其他自定义点。
如果用户想添加全新的模式,则可以专门化PatternTraits 。
要注意的一件事是, Id不是普通类型。它的任何副本都只是引用它。因此,请勿尝试从定义的位置返回它。
一个坏案子是
auto badId ()
{
Id< int > x;
return x;
}返回包括本地Id组成模式也不正确。
auto badPattern ()
{
Id< int > x;
return composeSomePattern (x);
}良好的做法是在模式匹配中定义Id接近其用法。
auto goodPattern ()
{
Id< int > x;
auto somePattern = composeSomePattern (x);
return match (...)
(
pattern | somePattern = ...
);
}mathiu是一个基于match(it)的简单计算机代数系统。
一个简单的mathiu样本:
auto const x = symbol( " x " );
auto const e = x ^ fraction( 2 , 3 );
auto const d = diff(e, x);
// prints (* 2/3 (^ x -1/3))
std::cout << toString(d) << std::endl;OpenNASK:小型OS的80x86汇编器,例如Masm/Nasm。
如果您知道使用此库的其他项目,请通过提交问题或公关来告诉我。
如果您对图书馆有任何疑问或想法,请打开问题。
欢迎讨论 /问题 / PR。
match(it)的语法 /模式设计受这些相关工作的影响很大
如果您对match(it)感兴趣,那么您也可能对HSPP感兴趣,将Haskell样式编程带到C ++。
请标记回购,分享回购或赞助商一美元,让我知道这个图书馆很重要。
感谢所有人的贡献代码和发送错误。
特别是由于以下贡献者:
Hugo Etchegoyen(@hugoetchegoyen)
感谢 @e-dant赞助该项目。