next up previous
Next: Views Implementation Up: The View Template Library Previous: Views Design


Views Usage

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 $A=[1,2,2,4,4]$ and $B=[2,3,4,4]$ 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 $A'=[[1],[2,2],[4,4]]$ and $B'=[[2],[3],[4,4]]$.

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 $C=[([1],[]),([2,2],[2]),([],[3]),([4,4],[4,4])]$.

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 $\max\{0,m-n\}$ elements from the first range with $m$ elements, ignoring the $m$ elements from the second range. Proceeding with the intersection operation for our example, we get $D=[[],[2],[],[4,4]]$.

Finally, the ranges need to be flattened out into a single view. This is done by a concatenation_view. In the example we get $E=[2,4,4]=A\cap B$.

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.


next up previous
Next: Views Implementation Up: The View Template Library Previous: Views Design
Martin Weiser 2000-09-29