Consider a container holding pointers to dynamically allocated
Widgets. Among the widgets are Buttons,
ScrollBars and so on, all derived from Widget. For an
operation working on buttons, you would like to have a container of
Buttons.
The procedure is the following: first test every widget pointer
whether or not it actually points to a button, then take only the
button pointers, and dereference them. The downcast_view from
VTL that does exactly this is built up from three more fundamental
components, each of which performs one of the three tasks mentioned
above.
On the ground layer, there is a
typedef transform_view<container_type,
downcast<Widget,Button> > downcasted;
presenting pointers to Buttons that result from a
dynamic_cast<Button>(...). To perform this dynamic cast is the
task of the transformation downcast<Widget, Button>. The
only non null pointers in this view are the ones that actually point
to buttons. Transform views are one of the most useful versatile
building blocks of the VTL. E.g. they can be used to extract the
values of a pair associative container by specifying
select_2nd<...> as transformation. This is realized in VTL as
the convenience view map_values.
On the middle layer, there is a
typedef filter_view<downcasted,
is_nonzero<Button*>,
...,
aggregated_ownership> filtered;
presenting only the non null pointers, i.e. exactly the pointers to
buttons. The selection is based on the predicate
is_nonzero<Button*>. Of course, the filtered view must own the
downcasted view exclusively or shared, otherwise the user would be
required to delete both the filtered view and the intermediate
downcasted view.
On the top layer, there is a
typedef transform_view<filtered,
dereference<Button*>,
...,
aggregated_ownership> dereferenced;
presenting references to the buttons instead of pointers. This
transform view with dereferencing transformation is convenient in its
own right, and is provided as polymorphic_view in the VTL.
Again, the dereferenced view must own the filtered view exclusively or
shared.
All this is packaged up into a single component, to be used as follows:
vector<Widget*> widgets; ... downcast_view<vector<Widget*>,Button> buttons(widgets); for_each(buttons.begin(),buttons.end(),use_button);The remaining unspecified template parameters default to read only access, the maximum justified iterator category, and external ownership.
The second example, though of probably little practical use, is
interesting because it demonstrates how views can be layered on top of
each other to create powerful tools. Given two sorted containers, the
usual generalized set operations intersection, union, difference, and
symmetric difference can be performed in linear time, as is done by
the corresponding STL algorithms. Following we will show how these
operations can be implemented on the fly using stacked views. For
simplicity, we will use the containers
and
for illustration.
The main insight is that all pairs of corresponding ranges of equal elements can be considered independently of one another when computing the set operations. Thus, a view that presents such pairs of corresponding ranges of equal elements is a basic building block for all the set operation views.
First, we have to identify the equal ranges in each container. This is
done by a basic view the iterators of which move two underlying
iterators appropriately through the underlying container. Applied to
the example data we get
and
.
Second, corresponding ranges in the two views have to be
identified. This is done by another basic view the iterators of which
move two iterators one to one through the two underlying
containers. The merged example data is then
.
On these pairs of ranges, a transformation specific to each set
operation creates an appropriate range from the pair. E.g. the
intersection transformation selects the shorter of the two ranges, and
the difference transformation takes
elements from the
first range with
elements, ignoring the
elements from the
second range. Proceeding with the intersection operation for our
example, we get
.
Finally, the ranges need to be flattened out into a single view. This
is done by a concatenation_view. In the example we get
.
Thus, all four set operations share the same stack of views, with a transformation function of a few lines of code that is plugged in to define the actual operation.