0

This is a follow-up to this question.

We can implement the visitor pattern for the problem in the previous question, as suggested in this answer:

class Base {
    foo(Parent& p) {
        p.accept(*this);
    }
    virtual void visit(Child_A&) = 0;
    virtual void visit(Child_B&) = 0;
};

class Parent {
    virtual void accept(Base&) = 0;
};

class Child_A: Parent {
    void accept(Base& v) {
        v.visit(*this);
    }
};

class Child_B: Parent {
    void accept(Base& v) {
        v.visit(*this);
    }
};

class Derived_A: Base { 
    void treat_same(Parent&) {
        // ...
    }
    void visit(Child_A& a) {
        treat_same(a);
    }
    void visit(Child_B& b) {
        treat_same(b);
    }
};
class Derived_B: Base { 
    void visit(Child_A&) {
        // ...
    }
    void visit(Child_B&) {
        // ...
    }
};

But now consider if foo expects a std::vector<std::shared_ptr<Parent>> const& as its argument. Then how can we implement visitor pattern for the problem? Is it possible?

EDIT

foo passes std::vector<std::shared_ptr<Parent>> const& to another class, state. But if all the objects in the vector are of type Child_A it calls state.method_A, if all objects in the vector are of type Child_B it calls state.method_B and otherwise calls state.method_C. Only state works directly with parent classes.

I hope this clears things a bit.

Eliad
  • 894
  • 6
  • 20
  • 1
    I don't understand what you're asking. What is it you want `foo` to do? – Barry Apr 18 '15 at 13:25
  • Also, not sure you can make a `std::vector`. – Barry Apr 18 '15 at 13:25
  • What behaviour do you want? You could visit each element in turn or treat the container as a whole. That said, what is this `foo()` in class `Base`? What is `treat_same()` in class `Derived_A` for? I'd also like to remark that calling things `Base` and `Derived` and `Parent` and `Child` doesn't make code clearer, why is there no e.g. `Visitor` baseclass instead? – Ulrich Eckhardt Apr 18 '15 at 13:26
  • I think the closest thing you can get with a `std::vector<>` is `std::vector>`, though be aware that ownership is transferred. – πάντα ῥεῖ Apr 18 '15 at 13:29
  • @Barry, I corrected it. – Eliad Apr 18 '15 at 13:48
  • @πάνταῥεῖ, Could you expand on that? I am a bit new to C++11. – Eliad Apr 18 '15 at 13:52
  • @Furihr You need to put *in this question* what your expectations are. What do you want `foo` to do with the `vector`? Dispatch on all of them? Print its size? – Barry Apr 18 '15 at 13:57
  • @πάνταῥεῖ, I had a typo in the question, which I just corrected. It was `std::vector> const&` and **not** `std::vector> const&` – Eliad Apr 18 '15 at 14:36
  • Do you want `void foo(std::vector> const& v) { for (auto&& p : v) p.accept(*this); }` ? – Oktalist Apr 18 '15 at 14:52

1 Answers1

0

I'm not quite sure I get what you are after, but I'll give it a go.

As I understand it you want something like the following in your code:

std::vector<std::shared_ptr<Parent>> children;
Base * handler = new Derived_A; // or new Derived_B.
for (child : children) {
    // You want this call to correctly distinguish 
    // between objects of Child_A and Child_B
    handler->visit(child); 

Unfortunately, C++ does not* allow you to do that. (*keep reading) Since function calls are determined by type and all children are of the type shared_ptr for the purpose of this loop.

Luckily, all is not lost.


You can do two things, both requireing to determining the "real" type of child at runtime.

Option 1: Add a field in Parent identifying how it should be handled.

This requires something along the lines of the following.

class Parent {
public:
    enum class SubType {
        A,
        B,
    };

    virtual void accept(Base &) = 0;

    // Subclasses must implement this to 
    // allow instances of base to correctly handle the objects.
    virtual SubType handle_as() const = 0;
};

The implementation of Base would then include something like the following:

class Base {
     void visit( shared_ptr<Parent> p ) {
         switch( p->handle_as() ) {
         case Parent::SubType::A:
             this->accept( *static_ptr_cast<Child_A>(p) );
             break;
         case Parent::SubType::B:
             this->accept( *static_ptr_cast<Child_B>(p) );
             break;
     }
     // In addition to your accept(Child_A &) accept(Child_B &) etc.
};

Option 2: Use RunTime Type Identification. (RTTI).

The other alternative it to use dynamic cast. Which will enable RTTI in your whole executable, this may be what you want in this case, but be aware that it does incur a small performance + executable size cost.

dynamic_cast will return a nullptr if the cast is illegal, otherwise it will return a valid pointer.

In short:

class Base {
     void visit( shared_ptr<Parent> p ) {
         if ( dynamic_ptr_cast<Child_A>(p) ) {
             this->accept( *dynamic_ptr_cast<Child_A>(p) );
         } 
         else if ( dynamic_ptr_cast<Child_B>(p) ) {
             this->accept( *dynamic_ptr_cast<Child_B>(p) );
         } 
     }
     // In addition to your accept(Child_A &) accept(Child_B &) etc.
};
Stian Svedenborg
  • 1,797
  • 11
  • 27