simple_match, 仅用于c++14的简单标头 Pattern 匹配

分享于 

16分钟阅读

GitHub

  繁體 雙語
Simple header only pattern matching for c++14
  • 源代码名称:simple_match
  • 源代码网址:http://www.github.com/jbandela/simple_match
  • simple_match源代码文档
  • simple_match源代码下载
  • Git URL:
    git://www.github.com/jbandela/simple_match.git
    Git Clone代码到本地:
    git clone http://www.github.com/jbandela/simple_match
    Subversion代码到本地:
    $ svn co --depth empty http://www.github.com/jbandela/simple_match
    Checked out revision 1.
    $ cd repo
    $ svn up trunk
    
    ,可以扩展的C++ Pattern 匹配库

    我最近一直在看Haskell和 Rust。 我在 C++ 中所需要的东西之一是 Pattern 匹配。

    下面是Rustlang书的一个例子( http://static.rust-lang.org/doc/master/book/match.html )

    match x {
     1=>println!("one"),
     2=>println!("two"),
     3=>println!("three"),
     4=>println!("four"),
     5=>println!("five"),
     _ =>println!("something else"),
    }

    现在有一个 C++ 库 Mach7,它做 Pattern 匹配( https://github.com/solodon4/Mach7 ),但是它很大,复杂,并且使用了很多 MACROS。 我想看看是否可以使用C++14编写一个没有宏的简单实现。

    这个库是这项工作的结果。 如果你熟悉 C++14,特别是可变模板。转发和元组,那么这个库和实现应该很容易理解和扩展。

    用法

    你将需要一个C++14编译器。 我使用最新的Visual C++ 2015 CTP。GCC 4.9.2和 Clang 3.5来测试这个库。

    库由 2个头组成。 simple_match.hpp 是库的核心,some_none.hpp 包含了允许你对原始指针。unique_ptr和shared_ptr进行 MATCH的代码。

    下面是一个简单的摘录。 假设你已经包含了 simple_match.hpp

    usingnamespacesimple_match;usingnamespacesimple_match::placeholders;int x = 0;while (true) {
     std::cin>> x;
     match(x,
     1, []() {std::cout <<"The answer is onen"; },
     2, []() {std::cout <<"The answer is twon"; },
     _x <10, [](auto&& a) {std::cout <<"The answer " <<a <<" is less than 10n"; },
     10 <_x <20, [](auto&& a) {std::cout <<"The answer " <<a <<" is between 10 and 20 exclusiven"; },
     _, []() {std::cout <<"Did not matchn"; }
     );
    }

    示例文件

    测试目录下有 2个文件: test.cppcppcon-matching.cpptest.cpp 只包含一些简单的匹配测试。 cppcon-matching.cpp 包含Mach7中的示例,该示例在 cppcon。

    扩展

    命名空间 simple_matcher::customization 中提供了 2个自定义项。 他们是

    template<classT, classU>structmatcher;

    template<classType>structtuple_adapter;

    许可证

    许可许可许可许可许可。

    教程

    我们假设你在文件的顶部有以下内容

    #incude "simple_match/simple_match.hpp"#incude "simple_match/some_none.hpp"usingnamespacesimple_match;usingnamespacesimple_match::placeholders;

    下面是如何确定 MATCH的方法

    int i = 0;match(i,
     1, [](){std::cout <<"The answer is one";}
     2, [](){std::cout <<"The answer is two";}
     otherwise, [](){std::cout <<"Did not match"}
    );

    MATCH 函数将尝试从上到下匹配,并运行与第一个成功的MATCH 对应的lamba。 otherwise 总是 MATCHES,因此你应该在最后拥有它。 如果你发现 otherwise 太长,你也可以使用 _。 它位于命名空间 simple_match::placeholders

    匹配也适用于字符串。

    
    std::string s ="";
    
    
    
    match(s,
    
    
    "Hello", [](){ std::cout <<" You said hellon";},
    
    
     _, [](){std::cout <<"I do not know what you saidn";}//_ is the same as otherwise
    
    
    );
    
    
    
    
    

    你甚至可以从匹配中返回值

    char digit = '0';int value = match(digit,
     '0', [](){return0;},
     '1', [](){return1;},
     '2', [](){return2;},
     '3', [](){return3;},
     '4', [](){return4;},
     '5', [](){return5;},// and so on);

    我们也可以进行比较和范围。 要这样做,请使用 simple_match::placeholders 命名空间中的_x

    int i = 0;match(i,
     _x <0, [](int x){std::cout <<x <<" is a negative numbern";},
     1 <_x <10, [](int z){std::cout <<z <<" is between 1 and 10n"},
     _x, [](int x){std::cout <<x <<" is the valuen";}
    );

    在 上面 示例中有一些感兴趣的项目。 当使用 _x 时,它将它的值传递给。 如果使用 _x 没有任何比较,它将把值传递给 lambda。 另外,由于重载的方式,使用 < 或者 <= 运算符在上面的MATCH 中使用范围非常容易。

    元组

    现在我们可以更有趣了 ! 让我们把 2d 点表示为 tuple。

    std::tuple<int,int> p(0,0);match(p,
     tup(0,0), [](){std::cout <<"Point is at the origin";},
     tup(0,_), [](){std::cout <<"Point is on the horizontal axis";},
     tup(_,0), [](){std::cout <<"Point is on the vertical axis";}.
     tup(_x <10,_), [](int x){std::cout <<x <<" is less than 10";},
     tup(_x,_x), [](int x, int y){ std::cout <<x <<"," <<y <<" Is not on an axis";}
    );

    tup 匹配 tuple 匹配注意,你可以使用相同的表达式,而不使用元组。 在 _x 产生一个值传递给lambda之前。 _ MATCHES 任何内容并忽略它,因此不会向lambda传递相应的变量。

    我们实际上可以使用 tup 来分解我们自己的structclass。 首先我们要专门 simple_match::customization::tuple_adapter 对于我们的类型。

    structpoint {
     int x;
     int y;
     point(int x_,int y_):x(x_),y(y_){}
    };// Adapting point to be used with tupnamespacesimple_match {
     namespacecustomization {
     template<>
     structtuple_adapter<point>{
     enum { tuple_len = 2 };
     template<size_t I, classT>
     staticdecltype(auto) get(T&& t) {
     return std::get<I>(std::tie(t.x,t.y));
     }
     };
     }
    }

    然后我们就可以像使用 tuple 那样使用 tup

    point p{0,0};match(p,
     tup(0,0), [](){std::cout <<"Point is at the origin";},
     tup(0,_), [](){std::cout <<"Point is on the horizontal axis";},
     tup(_,0), [](){std::cout <<"Point is on the vertical axis";}.
     tup(_x <10,_), [](int x){std::cout <<x <<" is less than 10";},
     tup(_x,_x), [](int x, int y){ std::cout <<x <<"," <<y <<" Is not on an axis";}
    );

    指针作为选项类型

    有时我们有一个指针,我们想要得到一个安全的值。 要做到这一点,我们可以使用 somenone。 要做到这一点,我们必须包括 simple_match/some_none.hpp

    让我们使用与前面相同的point

    point* pp = new point(0,0);match(pp,
     some(), [](point& p){std::cout <<p.x <<" is the x-value";}
     none(), [](){std::cout <<"Null pointern";}
    );

    注意 some() 如何将指针转换为引用并将它的传递给我们。

    现在,我们应该用一个全新的新方法来分配内存。 我们可能会使用 std::unique_ptrsome 已经支持 unique_ptrshared_ptr。 这样我们就可以像这样写。

    auto pp = std::make_unique<point>(0,0);match(pp,
     some(), [](point& p){std::cout <<p.x <<" is the x-value";}
     none(), [](){std::cout <<"Null pointern";}
    );

    注意,我们的MATCH 代码没有改变。

    我们可以做得更好因为 some 组成了。 由于我们专门的tuple_adapter,我们可以使用 tuppoint

    auto pp = std::make_unique<point>(0,0);match(pp,
     some(tup(0,0)), [](){std::cout <<"Point is at the origin";},
     some(tup(0,_)), [](){std::cout <<"Point is on the horizontal axis";},
     some(tup(_,0)), [](){std::cout <<"Point is on the vertical axis";}.
     some(tup(_x <10,_)), [](int x){std::cout <<x <<" is less than 10";},
     some(tup(_x,_x)), [](int x, int y){ std::cout <<x <<"," <<y <<" Is not on an axis";},
     none(), [](){std::cout <<"Null pointer";}
    );

    请注意 sometup 如何组成。 如果我们想要,我们可以在元组中有指针,指针中的元组和它只能工作。

    some 也可以使用RTTI来进行向下。

    下面是一个示例,我们将使 point 成为基类,并将,作为子类,并适应它们。

    structpoint{
     virtual~point(){}
    };structpoint2d:point{
     int x;
     int y;
     point2d(int x_,int y_):x(x_),y(y_){}
    };structpoint3d:point{
     int x;
     int y;
     int z;
     point3d(int x_,int y_, int z_):x(x_),y(y_),z(z_){}
    };// Adapting point2d and point3d to be used with tupnamespacesimple_match {
     namespacecustomization {
     template<>
     structtuple_adapter<point2d>{
     enum { tuple_len = 2 };
     template<size_t I, classT>
     staticdecltype(auto) get(T&& t) {
     return std::get<I>(std::tie(t.x,t.y));
     }
     };
     template<>
     structtuple_adapter<point3d>{
     enum { tuple_len = 3 };
     template<size_t I, classT>
     staticdecltype(auto) get(T&& t) {
     return std::get<I>(std::tie(t.x,t.y,t.z));
     }
     };
     }
    }

    这样我们就可以用它

    std::unique_ptr<point> pp(new point2d(0,0));match(pp,
     some<point2d>(tup(_x,_x)), [](int x, int y){std::cout <<x <<"," <<y;},
     some<point3d>(tup(_x,_x,_x)), [](int x, int y, int z){std::cout <<x <<"," <<y <<"," <<z;},
     some(), [](point& p){std::cout <<"Unknown point typen"},
     none(), [](){std::cout <<"Null pointern"}
    );

    注意我们如何安全地转换,并使用 tup 来对 point 进行 destructure。 一切都很完美。

    实现细节

    实际上simple_match比我想象中更容易实现。 我使用 http://isocpp.org/files/papers/N3915.pdf 中的应用示例实现来调用带有 tuple的函数作为参数。

    下面是实现的核心

    template<classT, classU>boolmatch_check(T&& t, U&& u) {
     usingnamespacecustomization;using m = matcher<std::decay_t<T>, std::decay_t<U>>;
     returnm::check(std::forward<T>(t), std::forward<U>(u));
    }template<classT, classU>automatch_get(T&& t, U&& u) {
     usingnamespacecustomization;using m = matcher<std::decay_t<T>, std::decay_t<U>>;
     returnm::get(std::forward<T>(t), std::forward<U>(u));
    }template<classT, classA1, classF1>automatch(T&& t, A1&& a, F1&& f) {
     if (match_check(std::forward<T>(t), std::forward<A1>(a))) {
     returndetail::apply(f, match_get(std::forward<T>(t), std::forward<A1>(a)));
     }
     else {
     throwstd::logic_error("No match");
     }
    }template<classT, classA1, classF1, classA2, classF2, class... Args>automatch(T&& t, A1&& a, F1&& f, A2&& a2, F2&& f2, Args&&... args) {
     if (match_check(t, a)) {
     returndetail::apply(f, match_get(std::forward<T>(t), std::forward<A1>(a)));
     }
     else {
     returnmatch(t, std::forward<A2>(a2), std::forward<F2>(f2), std::forward<Args>(args)...);
     }
    }

    match 是一个可变函数,它接受匹配的值,然后是 MATCH 标准的参数,如果条件成功,则执行要执行的lambda。 它通过调用 match_check,直到返回 true。 然后调用 match_get 获取需要转发到lambda的值的tuple,并使用应用来调用 lambda。

    MATCH 类型是通过专门化实现 simple_match::customization::matcher

    namespacecustomization {
     template<classT, classU>
     structmatcher;
    }

    例如 MATCHES 到值的匹配器是如何实现的。 注意,它不会向lambda传递任何值,因此返回空的tuple。

    // Match same typetemplate<classT>structmatcher<T, T> {
     staticboolcheck(const T& t, const T& v) {
     return t == v;
     }
     staticautoget(const T&, const T&) {
     returnstd::tie();
     }
    }

    我希望你喜欢使用这段代码,就像我在写它时所。 请给我任何你可能有的反馈。

    -- R。Bandela,MD

    StackEdit 写。


    相关文章