-
The ''Visitor Pattern'' is a special form of //double dispatch// — selecting the function actually to be executed at runtime based both on the concrete type of some tool object //and // the target this tool is applied to. The rationale is to separate some specific implementation details from the basic infrastructure and the global interfaces, which can be limited to describe the fundamental properties and operations, while all details relevant only for some specific sub-problem can be kept together encapsulated in a tool implementation class. Typically, there is some iteration mechanism, allowing to apply these tools to all objects in a given container, a collection or object graph, without knowing the exact type of the target and tool objects. See the [[Visitor Pattern design discussion|VisitorUse]]
+
+
The ''Visitor Pattern'' is a special form of //double dispatch// — the goal is to select the function actually to be executed at runtime based both on the concrete type of some tool object //and// the target this tool is applied to. The rationale is to separate some specific implementation details from the basic infrastructure and the global interfaces, which can be limited to describe the fundamental properties and operations, while all details relevant only for some specific sub-problem can be kept together encapsulated in a tool implementation class. Typically, there is some iteration mechanism, allowing to apply these tools to all objects in a given container, a collection or object graph, without knowing the exact type of the target and tool objects. See the [[Visitor Pattern design discussion|VisitorUse]]
!Problems with Visitor
-The visitor pattern is not very popular, because any implementation is tricky, difficult to understand and often puts quite some burden on the user code. Even Erich Gamma says that on his list of bottom-ten patterns, Visitor is at the very bottom. This may be due to the fact that this original visitor implementation (often called the ''~GoF visitor'') causes a cyclic dependency between the target objects and the visiting tool objects, and includes some repetitive code (which results in silent failure if forgotten). Robert Martin invented 1996 an interesting variation (commonly labled ''acyclic visitor''). By using a marker base class for each concrete target type to be treated by the visitor, and by applying a dynamic cast, we can get rid of the cyclic dependencies. The top level "Visitor" is reduced to a mere empty marker interface in this design, while the actual visiting capabilities for a concrete target object is discovered at application time by the aforementioned dynamic cast. Besides the runntime cost of such a cast, the catch is that now the user code has still more responsibilities, because the need to maintain consistently two parallel object hierarchies.
-Long time there seemed to be not much room for improvement, at least before the advent of generic programming and the discovery of template metaprogramming. ''Loki'' (Alexandrescu, 2000) showed us how to write a library implementation which hides away the technicallities of the visitor pattern and automatically generates most of the repetitive code.
+The visitor pattern is not very popular, because any implementation is tricky, difficult to understand and tends to place quite some burden on the user code. Even Erich Gamma says that on his list of bottom-ten patterns, Visitor is at the very bottom. This may be due to the fact that this original visitor implementation (often called the ''~GoF visitor'') causes a cyclic dependency between the target objects and the visiting tool objects, and includes some repetitive code (which results in silent failure if forgotten). Robert Martin invented 1996 an interesting variation (commonly labelled ''acyclic visitor''). By using a marker base class for each concrete target type to be treated by the visitor, and by applying a dynamic cast, we can get rid of the cyclic dependencies. The top level "Visitor" is reduced to a mere empty marker interface in this design, while the actual visiting capabilities for a concrete target object is discovered at application time by the aforementioned dynamic cast. Besides the runtime cost of such a cast, the catch is that now the user code has still more responsibilities, because the need to maintain consistently two parallel object hierarchies.
+Long time there seemed to be not much room for improvement, at least before the advent of generic programming and the discovery of template metaprogramming. ''Loki'' (Alexandrescu, 2000) showed us how to write a library implementation which hides away the technicalities of the visitor pattern and automatically generates most of the repetitive code.
!Requirements
* cyclic dependencies should be avoided or at least restricted to some central, library related place.
* The responsibilities for user code should be as small as possible. Especially, we should minimize the necessity to do corresponding adjustments to separate code locations, e.g. the necessity to maintain parallel hierarchies.
* Visitor is about //double dispatch,// thus we can't avoid using some table lookup implementation — more specifically we can't avoid using the cooperating classes vtables. We can expect at least two table lookups for each call dispatch. Besides that, the implementation should not be too wasteful...
-* individual "visiting tool" implementation classes should be able to opt in or opt out on implementing functions treating some of the visitable subclasses.
+* individual "visiting tool" implementation classes should be able to opt in or opt out on implementing functions to treat some of the visitable subclasses.
* there should be a safe fallback mechanism backed by the visitable object's hierarchy relations. If some concrete visiting tool class decides not to implement a {{{treat(..)}}}-function for some concrete target type, the call should fall back to the next best match according to the target object's hierarchy, i.e. the next best {{{treat(..)}}}-function should be used.
-The last requirement practically rules out the Loki acyclic visitor, because this implementation calls a general fallback function when an exact match based on the target object's type is not possible. Considering our use of the visitor pattern within the render engine builder, such a solution would not be of much use: Some specific builder tool may implement a {{{treat(CompoundClip&)}}}-function, while most of the other builder tools just implement a {{{treat(Clip&)}}}-function, thus handling any multichannel clip via the general clip interface. This is exactly the reason why we want to use visitor at first place. Isolate specific treatment, implement against interfaces.
+The last requirement practically rules out the Loki acyclic visitor, because this implementation calls a general fallback function when an exact match based on the target object's type is not possible. Considering our use of the visitor pattern within the render engine builder, such a solution would not be of much use: Some specific builder tool may implement a {{{treat(CompoundClip&)}}}-function, while most of the other builder tools just implement a {{{treat(Clip&)}}}-function, thus handling any multichannel clip via the general clip interface. This is exactly the reason why we want to use visitor at first place. Reduce the scope of specific treatment, implement against rather generic interfaces.
!Implementation Notes
-A good starting point for understanding our library implementation of the visitor pattern is {{{tests/components/common/visitingtoolconcept.cpp}}}, which contains a all-in-one-file proof of concept implementation, on which the real implementation ({{{"common/visitor.hpp"}}}) was based.
+A good starting point to understand our library implementation of the visitor pattern is {{{tests/components/common/visitingtoolconcept.cpp}}}, which contains an complete, all-in-one-file proof of concept implementation, on which the real implementation ({{{"common/visitor.hpp"}}}) was based.
* similar to Loki, we use a {{{Visitable}}} base class and a {{{Tool}}} base class (we prefer the name "Tool" over "Visitor", because it makes the intended use more clear).
* the objects in the {{{Visitable}}} hierarchy implement an {{{apply(Tool&)}}}-function. This function needs to be implemented in a very specific manner, thus the {{{DEFINE_PROCESSABLE_BY}}} macro should be used when possible to insert the definition into a concrete {{{Visitable}}} class.
* similar to the acyclic visitor, the concrete visiting tool classes inherit from {{{Applicable<TARGET, ...>}}} marker base classes, where the template parameter {{{TARGET}}} is the concrete Visitable type this tool wants to treat, either directly by defining a {{{treat(ConcreteVisitable&)}}}, or by falling back to some more general {{{treat(...)}}} function.
* consequently our implementation is //rather not acyclic// — the concrete tool implementation depends on the full definition (header) of all concrete Visitables, but we avoid cyclic dependencies on the interface level. By using a typelist technique inspired by Loki, we can concentrate these dependencies in one single header file, which keeps things maintainable.
-* we use the {{{Applicable<TARGET, ...>}}} marker base classes to drive the generation of Dispatcher classes, each of which holds a table of trampoline functions for carrying out the actual double dispatch at call time. Each concrete Visitable using the {{{DEFINE_PROCESSABLE_BY}}}-macro generates a separate Dispatcher table containing slots for each concrete tool implementation class. To store and access the index position for these "call slots", we use a tag associated with the concrete visiting tool class, which can be retrieved by going though the tool's vtable
+* we use the {{{Applicable<TARGET, ...>}}} marker base classes to drive the generation of Dispatcher classes, each of which holds a table of trampoline functions to carry out the actual double dispatch at call time. Each concrete Visitable, by using the {{{DEFINE_PROCESSABLE_BY}}}-macro, causes the generation of a dedicated, separate Dispatcher table, holding call slots for each concrete tool implementation class. To store and access the index position for these "call slots", we use a tag associated with the concrete visiting tool class, which can be retrieved by going though the tool's vtable
* __runtime cost__: the concrete tool's ctor stores the trampoline pointers (this could be optimized to be a "once per class" initialisation). Then, for each call, we have 2 virtual function calls and a lookup and call of the trampoline function, typically resulting in another virtual function call for resolving the {{{treat(..)}}} function on the concrete tool class
-* __extension possibilities__: while this system may seem complicated, you should note that it was designed with special focus on extension and implementing against interfaces:
-** not every Visitable subclass needs a separate Dispatcher. As a rule of thumb, only when some type needs to be treated separately within some concrete visiting tool (i.e. when there is the need of a {{{treat(MySpecialVisitable&)}}}), then this class should use the {{{DEFINE_PROCESSABLE_BY}}}-macro and thus define it's own {{{apply()}}}-function and Dispatcher. In all other cases, it is sufficient just to extend some existing Visitable, which thus will act as an interface with regards to the visiting tools.
-** because the possibility of utilizing virtual {{{treat(...)}}} functions, not every concrete visiting tool class needs to define a set of {{{Applicable<...>}}} base classes (and thus get a separate dispatcher slot). We need such only for each //unique set// of Applicables. All other concrete tools can extend existing tool implementations, sharing and partially extending the same set of virtual {{{treat()}}}-functions.
+* __extension possibilities__: while this system might seem overengineered, you should note the specific focus on extension and implementing against interfaces:
+** not every Visitable subclass requires to build a separate Dispatcher. As a rule of thumb, only when a class needs dedicated and specific treatment within some concrete visiting tool (i.e. when there is the need of a function {{{treat(MySpecialVisitable&)}}}), then this class should use the {{{DEFINE_PROCESSABLE_BY}}}-macro, leading to the definition of a distinct {{{apply()}}}-function and Dispatcher. In all other cases, it is sufficient just to extend some existing Visitable, which thus acts as an interface as far as visiting tools are concerned.
+** because of the possibility of utilising virtual {{{treat(...)}}} functions, not every concrete visiting tool class needs to define a set of {{{Applicable<...>}}} base classes (and thus get a separate dispatcher slot). We need such only for each //unique set// of Applicables. All other concrete tools can extend existing tool implementations, sharing and partially extending the same set of virtual {{{treat()}}}-functions.
** when adding a new "first class" Visitable, i.e. a concrete target class that needs to be treated separately in some visiting tool, the user needs to include the {{{DEFINE_PROCESSABLE_BY}}} macro and needs to make sure that all existing "first class" tool implementation classes include the Applicable base class for this new type. In this respect, our implementation is clearly "cyclic". (Generally speaking, the visitor pattern should not be used when the hierarchy of target objects is frequently extended and remoulded). But, when using the typelist facillity to define the Applicable base classes, we'll have one header file defining these collection of Applicables and thus we just need to add our new concrete Visitable to this header and recompile all tool implementation classes.
** when creating a new "~Visitable-and-Tool" hierarchy, the user should derive (or typedef) and parametrize the {{{Visitable}}}, {{{Tool}}} and {{{Applicable}}} templates, typically into a new namespace. An example can be seen in {{{proc/mobject/builder/buildertool.hpp}}}