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?
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!