c++polymorphismvisitor-pattern

Polymorphism with a dereferenced pointer yields unexpected results...why?


I've run into a C++ conundrum and could use some help!

Please consider the following code:

#include <iostream>

struct Node
{
    virtual void print() 
    {
        std::cout << "Node::print" << std::endl;
    }
};

struct Drawable : public Node
{
    virtual void print() 
    {
        std::cout << "Drawable::print" << std::endl;
    }
};

struct Visitor
{
    virtual void apply(Node& node)
    {
        std::cout << "apply(Node&)" << std::endl;
        node.print();
    }
    virtual void apply(Drawable& drawable) 
    {
        std::cout << "apply(Drawable&)" << std::endl;
        drawable.print();
    }
};

struct Renderer
{
    virtual void accept(Node* node, Visitor* visitor)
    {
        visitor->apply(*node);
    }
};

int main(int argc, char** argv)
{
    Renderer r;
    Visitor v;
    Drawable* drawable = new Drawable();
    r.accept(drawable, &v);
    return 0;
}

The output is:

apply(Node&)
Drawable::print

I was expecting Visitor::apply(Drawable&) to be invoked, but instead apply(Node&) is invoked. Why?


Solution

  • Take a look at this line of code:

    virtual void accept(Node* node, Visitor* visitor)
    {
        visitor->apply(*node); // <--- Here
    }
    

    When the compiler sees this call to apply, it needs to determine whether you meant to call the version of apply that takes in a Node& or the version of apply that takes in a Drawable&. Notice that this is a decision about which overload to pick rather than which override to pick. Decisions about overloading are made at compile-time. Here, the compiler looks at the expression *node and says "well, node is a Node *, so *node is a Node&" because it can't know at compile-time what the runtime type of the thing pointed at by node is. As a result, this will always call apply(Node &) and will never call apply(Drawable&).

    This explains what's going on, but how do you fix it? The conventional way to get the visitor pattern set up is to place a function like this one at the base of the class hierarchy:

    struct Node {
        virtual void accept(Visitor* visitor);
    };
    

    You'd then have every subclass of Node override that function. Each override would then look something like this:

    struct Pizkwat: Node {
        virtual void accept(Visitor* visitor) override {
            visitor->apply(*this);
            // do more things, optionally
        }
    };
    

    In the line of code in this override, the type of *this will at compile-time be the same type as the object in question. That means that overload selection will pick the version of apply most specific to this type.

    To use this accept function, you can write something like this:

    virtual void accept(Node* node, Visitor* visitor)
    {
        node->accept(visitor);
    }
    

    Now, think about what happens. The accept member function is marked virtual, so the specific version of accept to call depends on the type of the thing pointed at by node (that is, it uses the dynamic type rather than the static type). This is using override resolution rather than overload resolution. That will then call into the proper override, which, as you saw above, then selects the proper overload.

    Hope this helps!