C ++中的模式匹配风格?

时间:2010-02-07 10:17:32

标签: c++ macros pattern-matching

我喜欢Haskell风格模式匹配。

我的C ++代码如下:

ObjectPtr ptr;
if(ptr.isType<Foo>()) { // isType returns a bool
  Ptr<Foo> p = ptr.convertAs<Foo>(); // convertAs returns a Ptr<Foo>
  ......
}
if(ptr.isType<Bar>()) {
  Ptr<Bar> p = ptr.convertAs<Bar>();
  ......
}

现在,有没有我可以定义的宏来简化这个?我一直在思考这个问题,但不能进一步简化它。

谢谢!

6 个答案:

答案 0 :(得分:7)

dynamic_cast似乎可以做你想做的事情

struct A {
  virtual ~A() {}
};

struct B : struct  A { ... };
struct C : struct  A { ... };

A * a = new C;

if ( C * c = dynamic_cast<C*>( a ) ) {
   c->someCfunc();
}
else if ( B * b = dynamic_cast<B*>( a ) ) {
   b->someBfunc();
}
else {
   throw "Don't know that type";
}

答案 1 :(得分:7)

  

我喜欢Haskell风格模式匹配。

然后在Haskell中编写程序。

您要做的是切换类型。如果他们想要避免虚函数,这是人们常做的事情。现在,后者是C ++中OO的基石。如果你想避免它们,你为什么要用C ++编程?


至于为什么这是不赞成的:想象一下,你有很多像这样的代码

if(ptr.isType<Foo>()) ...
if(ptr.isType<Bar>()) ... 

涂抹了整个代码,然后有人来Baz添加ptr可能代表的可能类型。现在你正在寻找一个大的代码库,试图找到你切换类型的所有地方,并试图找出你需要添加Baz的地方。

而且,正如墨菲所说的那样,就在你完成时,Foz也会被添加为一种类型。 (或者,再想一想,如果墨菲以他的方式在之前悄悄地进入你有机会完全添加Baz。)

答案 2 :(得分:5)

尝试使用RTTI在C ++中模拟模式匹配样式是一个很好的想法,但它必然会有缺点,因为Haskell和Standard ML样式类型构造函数和C ++子类之间存在一些显着差异。 (注意:下面,我使用标准ML语法,因为我对它更熟悉。)

  • 在Haskell和Standard ML中,模式匹配可以将嵌套值绑定到模式变量(例如,模式a::b::c::ds将列表的前三个元素绑定到ab,和c,以及列表的其余部分ds)。在C ++中,你仍然需要在实际的嵌套结构中进行挖掘,除非你或其他人提出了比这里提出的更复杂的宏。
  • 在Haskell和Standard ML中,像datatype 'a option = NONE | SOME of 'a这样的类型构造函数数据类型声明定义了一种新类型:'a option。构造函数NONESOME不是类型,它们分别是类型'a option'a -> 'a option的值。在C ++中,当您定义类似FooBar的子类来模拟类型构造函数时,您将获得新类型。
  • 在Haskell和Standard ML中,像SOME这样的构造函数是构造它们所属的数据类型值的第一类函数。例如,map SOME的类型为'a list -> 'a option list。在C ++中,使用子类来模拟类型构造函数,您无法获得此功能。
  • 在Haskell和Standard ML中,数据类型是关闭的,因此没有人可以在不更改原始声明的情况下添加更多类型构造函数,并且编译器可以在编译时验证模式匹配是否处理所有情况。在C ++中,你必须尽可能地限制谁可以继承你的基类。

最后,与以更典型的方式使用C ++多态性相比,您是否从模拟模式匹配中获得了足够的好处?是否使用宏来使模拟模式匹配更简洁(同时为其他读取代码的人进行模糊处理)是值得的?

答案 3 :(得分:4)

我们共同撰写了一个用于C ++的模式匹配库,它允许您非常有效地进行模式匹配和类型分析。这个名为Mach7的库已经在BSD许可下发布,可以在GitHub上找到:https://github.com/solodon4/Mach7。您可以在那里找到视频,海报,幻灯片,论文以及源代码。它目前支持GCC 4.4 +,Clang 3.4+和Visual C ++ 2010+。通过针对其存储库提交GitHub问题,随意询问有关该库的问题。

答案 4 :(得分:3)

我假设您的Ptr模板具有NULL指针的概念。

ObjectPtr ptr;
if(Ptr<Foo> p = ptr.convertAs<Foo>()) { // convertAs returns a NULL pointer if the conversion can't be done.
  ......
}
if(Ptr<Bar> p = ptr.convertAs<Bar>()) {
  ......
}

尽管正如其他人所说,开启类型通常表明你在C ++中做错了。您应该考虑使用虚拟功能。

答案 5 :(得分:2)

认为这个宏正是你想要的:

#define DYN_IF(dest_type, dest_ptr, src_ptr)                                 \
    if((src_ptr).isType<dest_type>())                                        \
        if(int dest_type##dest_ptr = 1)                                      \
        for(Ptr<dest_type> dest_ptr = (src_ptr).convertAs<dest_type>();      \
            dest_type##dest_ptr;                                             \
            dest_type##dest_ptr=0)                                           

用法:

ObjectPtr ptr;
DYN_IF(Foo, foo_ptr, ptr) { 
    // foo_ptr is Ptr<Foo>
}
DYN_IF(Bar, bar_ptr, ptr)  // Works without braces too for single statement 
    // bar_ptr is Ptr<Bar>

我不会在其他人阅读的代码中推荐这种东西,但是因为你提到了“宏”这个词......

另外,我不会假装这与Haskell / OCaml风格的模式匹配有任何关系。如果你想要一种语言类似于C ++(嗯,那种)和真正的模式匹配的语言,请检查Scala。