next up previous
Next: Acknowledgement Up: The View Template Library Previous: Feature Diagrams


transform_view Definition

For illustration we present a nearly complete definition of transform_view, with some details stripped off to increase readability (e.g. namespaces).

First we define a template for the smart iterator that actually does the transformation on dereferentiation.

template <class transform,
          class iter_t,
          class T>
class transform_iterator
  : public iterator<iterator_traits<iter_t>::iterator_category,
                    T,
                    iterator_traits<iter_t>::difference_type,
                    pointer_traits<T>::type,
                    T> {
public:
  typedef iterator<iterator_traits<iter_t>::iterator_category,
                   T, iterator_traits<iter_t>::difference_type,
                   pointer_traits<T>::type,T> inherited;
  typedef typename const_traits<reference>::type const_reference;
Iterators must be default constructible.
  explicit transform_iterator() {}

  transform_iterator(transform trans_, iter_t iter_)
    : iter(iter_), trans(trans_) {}
Incrementing the transform iterators just increments the underlying iterator.
  transform_iterator& operator++()    { 
    ++iter; 
    return *this; 
  }
  transform_iterator  operator++(int) { 
    transform_iterator tmp(*this); 
    ++iter; 
    return *tmp; 
  }
Decrementing the transform iterators just decrements the underlying iterator. Therefore the decrement operators are only instantiable if the underlying iterator is at least bidirectional.
  transform_iterator& operator--()    { 
    --iter; 
    return *this; 
  }
  transform_iterator  operator--(int) { 
    transform_iterator tmp(*this); 
    --iter; 
    return *tmp; 
  }
Dereferencing the iterator does the main work, i.e. it applies the transformation to the value in the underlying container and returns a temporary result object.
  const_reference operator*() const { return trans(*iter); }
  reference       operator*()       { return trans(*iter); }
Iterator arithmetics is forwarded to the underlying iterator and since we use std::advance will be done as efficiently as possible. Note that because we determine the transform_iterator category based on the underlying iterator category, std::advance(transform_iter,n) will not call these operators unless the underlying iterator supports them.
  transform_iterator& operator+=(int n) { 
    advance(iter,n); 
    return *this; 
  }
  transform_iterator operator+(difference_type n) const {
    transform_iterator tmp(*this);
    return tmp += n;
  }
  transform_iterator& operator-=(int n) { 
    advance(iter,-n); 
    return *this; 
  }
  transform_iterator operator-(difference_type n) const {
    transform_iterator tmp(*this);
    return tmp -= n;
  }
  const_reference operator[](difference_type n) const { 
    return *(*this + n); 
  }

  difference_type operator-(const transform_iterator& y ) const {
    if (iter < y.iter) return -std::distance(iter,y.iter);
    else               return std::distance(y.iter,iter);
  }
Comparison operators are templated to allow comparison between mutable and immutable iterator variants. More exactly, the underlying iterators must be comparable.
  template <class iter2, class T2>
  bool operator==(const transform_iterator<transform,
                                           iter2,T2>& rhs) const { 
    return iter==rhs.iter; 
  }

  template <class iter2, class T2>
  bool operator< (const transform_iterator<transform,
                                           iter2,T2>& rhs) const { 
    return iter < rhs.iter; 
  }
Conversion from mutable to immutable iterator. Instantiation requires the conversion from iter_t to const_iter and T to const_T.
  template<class const_iter, class const_T>
  operator transform_iterator<transform,const_iter,
                              const_T>() const {
    return transform_iterator<transform,const_iter,
                              const_T>(trans,iter); 
  }
The smart iterator provides access to the underlying iterator.
  iter_t base() const { return iter; }
The smart iterator encapsulates an underlying iterator as well as the transformation to be applied.
private:
  iter_t iter;
  transform trans;

  template<class trans2, class iter2, class T2>
  friend class transform_iterator;
};

Second, the view template itself is defined. It provides type definitions for its iterator types, iterator generation methods and meta data methods.

template <class container,
          class transformation,
          class const_tag,
          class ownership_tag,
          class T>
class transform_view {
public:
  typedef one_container_base<container,
                             const_tag,ownership_tag> proxy_t;
  typedef T                              value_type;
  typedef typename const_traits<T>::type const_value_type;
  
  typedef typename ctor_arg<container,const_tag,
                            ownership_tag>::type ctor_arg_type;

  typedef view_traits<container,const_tag> vt;
  typedef typename vt::container_type domain_type;
  typedef typename vt::iterator       domain_iterator;
  typedef typename vt::const_iterator domain_const_iterator;

  typedef transformation transform_type;
  typedef typename pointer_traits<T>::type  pointer;
  typedef T                                 reference;
  typedef typename const_traits<T>::type    const_reference;
  typedef typename domain_type::size_type size_type;
  typedef typename domain_type::difference_type difference_type;

  typedef transform_iterator<transform_type, 
                             domain_iterator,       
                             value_type>          iterator;
  typedef transform_iterator<transform_type,       
                             domain_const_iterator,
                             const_value_type>    const_iterator;

  typedef reverse_iterator<iterator>       reverse_iterator;
  typedef reverse_iterator<const_iterator> const_reverse_iterator;
Views must be default constructible, although a default constructed views is good for nothing except subsequent assignment.
  explicit transform_view() {}

  transform_view(ctor_arg_type& cont, const transform_type& tr)
    : proxy(cont), trans(tr) {}
The iterator factory methods follow the standard container interface.
  const_iterator begin() const { 
    return const_iterator(trans,proxy.cont().begin()); 
  }
  const_iterator end()   const { 
    return const_iterator(trans,proxy.cont().end()); 
  }
  iterator begin() { 
    return iterator(trans,proxy.cont().begin()); 
  }
  iterator end()   { 
    return iterator(trans,proxy.cont().end()); 
  }
Reverse iterator factory methods require the underlying container to be reversible.
  const_reverse_iterator rbegin() const { 
    return const_reverse_iterator(end()); 
  }
  const_reverse_iterator rend()   const { 
    return const_reverse_iterator(begin()); 
  }
  reverse_iterator rbegin() { 
    return reverse_iterator(end()); 
  }
  reverse_iterator rend()   { 
    return reverse_iterator(begin()); 
  }
The usual container meta information is provided as well.
  size_type size()     const { return proxy.cont().size(); }
  size_type max_size() const { return proxy.cont().max_size(); }
  bool      empty()    const { return proxy.cont().empty(); }
If the underlying container is a sequence or back insertion sequence, the front() and back() methods can be instantiated.
  const_reference front() const { 
    return trans(proxy.cont().front()); 
  }
  const_reference back()  const { 
    return trans(proxy.cont().back()); 
  }
  reference       front()       { 
    return trans(proxy.cont().front()); 
  }
  reference       back()        { 
    return trans(proxy.cont().back()); 
  }
Subscription operators require the underlying container to be random accessable.
  const_reference operator[](size_type n) const { 
    return trans(proxy.cont()[n]); 
  }
  reference       operator[](size_type n)       { 
    return trans(proxy.cont()[n]); 
  }
  const_reference at(size_type n) const { 
    range_check(n); 
    return (*this)[n]; 
  }
  reference       at(size_type n)       { 
    range_check(n); 
    return (*this)[n]; 
  }
Erasing elements from the view is done by erasing them in the underlying container. Inserting elements would require an inverse transformation.
  iterator erase(iterator first, iterator last) {
    return iterator(proxy.cont().erase(first.base(),last.base()));
  }
Popping elements from the ends of the view requires the underlying container to be a front or back insertion sequence.
  void pop_front() { proxy.cont().pop_front(); }
  void pop_back() { proxy.cont().pop_back(); }
A swap method easing the definition of the free standing swap function.
  void swap(transform_view& b) {
    swap(proxy,b.proxy);
    swap(trans,b.trans);
  }
The transform view encapsulates (a reference to) the underlying container and the transformation to be applied.
private:
  proxy_t proxy;
  transformation trans;

  void range_check(size_type n) const {
    if (n < 0 || n >= size()) 
      throw std::range_error("transform_view");
  }
};

Finally, views have to be equality and less than comparable. For this purpose we define non-member comparison operators.

template <class container_1,class container_2,
          class transformation_1,class transformation_2,
          class const_tag_1,class const_tag_2,
          class ownership_tag_1,
          class ownership_tag_2,
          class T_1,class T_2>
bool operator==(transform_view<container_1,
                               transformation_1,
                               const_tag_1,
                               ownership_tag_1,T_1> const & lhs,
                transform_view<container_2,
                               transformation_2,
                               const_tag_2,
                               ownership_tag_2,T_2> const & rhs) {
  return lhs.size() == rhs.size() && 
         equal(lhs.begin(), lhs.end(), rhs.begin());
}


template <class container_1,class container_2,
          class transformation_1,class transformation_2,
          class const_tag_1,class const_tag_2,
          class ownership_tag_1,
          class ownership_tag_2,
          class T_1,class T_2>
bool operator<(transform_view<container_1,
                              transformation_1,
                              const_tag_1,
                              ownership_tag_1,T_1> const & lhs,
               transform_view<container_2,
                              transformation_2,
                              const_tag_2,
                              ownership_tag_2,T_2> const & rhs) {
  return lexicographical_compare(lhs.begin(),lhs.end(), 
                                 rhs.begin(),rhs.end());
}
Views must also be swapable.
template <class container,
          class transformation,
          class const_tag,
          class ownership_tag,
          class T>
void swap(transform_view<container,transformation,
                         const_tag,ownership_tag,T>& a,
          transform_view<container,transformation,
                         const_tag,ownership_tag,T>& b) {
  a.swap(b);
}



next up previous
Next: Acknowledgement Up: The View Template Library Previous: Feature Diagrams
Martin Weiser 2000-09-29