views:

199

answers:

5

I love Haskell style pattern matching.

I have my C++ code as follows:

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>();
  ......
}

Now, are there any macros I can do define to simplify this? I have been pondering this for a while, but can't simplify it further.

Thanks!

+6  A: 

dynamic_cast would appear to do what you want

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";
}
anon
+1 for effort. However, I'm using smart pointers.
anon
Then do a dynamic cast on the contained pointer - for example, if you are using std::auto_ptr(), do it on the result of calling get().
anon
+8  A: 

I love Haskell style pattern matching.

Then write your program in Haskell.

What you're trying to do is a switch over a type. That's a common thing people do if they want to avoid virtual functions. Now, the latter are a cornerstone of what OO in C++ is all about. If you want to avoid them, why do you program in C++?


As for why this is frowned upon: Imagine you have a lot of code like this

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

smeared all over your code and then someone comes and adds Baz to the possible types that ptr might represent. Now you're hunting through a big code base, trying to find all those places where you switched over a type, and trying to find out which ones you need to add Baz to.

And, as Murphy has it, just when your done, there comes along Foz to be added as a type, too. (Or, thinking again, if Murphy has his way it creeps in before you had a chance too complete adding Baz.)

sbi
Low level memory control of C. Constructors standard template library (does any of this use virtual functions?). +1 assuming answer was genuine question, not troll.
anon
It certainly wasn't a genuine question, but neither was it a troll. It was a rhetorical question. To be plain: _You shouldn't do this in C++_. That's what virtual functions were invented for; they put all the code relating to objects of the type `Foo` into `Foo` itself, and everything relating to `Baz` into `Baz`. This isn't always the best you can get (witness, for example, the motivation behind the visitor pattern), but it's the default you should start with in C++.
sbi
+1  A: 

I'm assuming that your Ptr template has the concept of a NULL pointer.

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>()) {
  ......
}

Though, as others have noted, switching on type is usually a sign you're doing something wrong in C++. You ought to consider using virtual functions instead.

Omnifarious
Doesn't this just raise another question - "how to I write convertAs() to do that?"
anon
@Neil:Not really -- his original code assumed the existence of a convertAs. This would presumably use a dynamic_cast, which produces a null pointer if it can't convert a pointer to the target type. As such, chances are pretty good that it's already present, or fairly easy to implement if not present.
Jerry Coffin
The best part is, in C++0x you can replace `Ptr<Foo>` with `auto`.
Omnifarious
+2  A: 

A think this macro does precisely what you want:

#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)                                           

Usage:

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>

I wouldn't recommend this sort of stuff in code that is meant to be read by somebody else, but since you mentioned the word "macro"...

Also, I wouldn't pretend this has anything to do with pattern matching in the Haskell/OCaml style. Check Scala if you want a language that has semantics similar to C++ (well, sort of) and true pattern matching.

Manuel
+3  A: 

Attempting to simulate a pattern matching style in C++ using RTTI is a neat idea, but it's bound to have shortcomings, because there are some significant differences between Haskell and Standard ML style type constructors and C++ subclasses. (Note: below, I use Standard ML syntax because I'm more comfortable with it.)

  • In Haskell and Standard ML, pattern matching can bind nested values to pattern variables for you (e.g. the pattern a::b::c::ds binds the first three elements of the list to a, b, and c, and the rest of the list to ds). In C++, you'll still have to dig around in the actual nested structures, unless you or someone else comes up with far more complicated macros than have been proposed here.
  • In Haskell and Standard ML, a type constructor datatype declaration like datatype 'a option = NONE | SOME of 'a defines one new type: 'a option. Constructors NONE and SOME are not types, they are values with types 'a option and 'a -> 'a option, respectively. In C++, when you define subclasses like Foo and Bar to simulate type constructors, you get new types.
  • In Haskell and Standard ML, constructors like SOME are first-class functions that construct values of the datatype to which they belong. For example, map SOME has the type 'a list -> 'a option list. In C++, using subclasses to simulate type constructors, you don't get this ability.
  • In Haskell and Standard ML, datatypes are closed, so no one can add more type constructors without changing the original declaration, and the compiler can verify at compile time that the pattern match handles all cases. In C++, you have to go well out of your way to restrict who can subclass your base class.

In the end, are you getting enough benefit from simulated pattern matching compared to using C++ polymorphism in a more typical way? Is using macros to make simulated pattern matching slightly more concise (while obfuscating it for everyone else who reads your code) worthwhile?

bk1e