text | 507 lines | 385 code | 122 blank | 0 comment | 0 complexity | f2bf8bcc2f6b171141aa96f6b12fa1c5 MD5 | raw file
1
3
4In this section we'll walk through the implementation of a few
5iterators using `iterator_facade`, based around the simple
6example of a linked list of polymorphic objects.  This example was
7inspired by a
9by Keith Macdonald on the
10[@http://www.boost.org/more/mailing_lists.htm#users `Boost-Users`]
11mailing list.
12
13
14[h2 The Problem]
15
16
17Say we've written a polymorphic linked list node base class:
18
19  # include <iostream>
20
21  struct node_base
22  {
23      node_base() : m_next(0) {}
24
25      // Each node manages all of its tail nodes
26      virtual ~node_base() { delete m_next; }
27
28      // Access the rest of the list
29      node_base* next() const { return m_next; }
30
31      // print to the stream
32      virtual void print(std::ostream& s) const = 0;
33
34      // double the value
35      virtual void double_me() = 0;
36
37      void append(node_base* p)
38      {
39          if (m_next)
40              m_next->append(p);
41          else
42              m_next = p;
43      }
44
45   private:
46      node_base* m_next;
47  };
48
49Lists can hold objects of different types by linking together
50specializations of the following template:
51
52  template <class T>
53  struct node : node_base
54  {
55      node(T x)
56        : m_value(x)
57      {}
58
59      void print(std::ostream& s) const { s << this->m_value; }
60      void double_me() { m_value += m_value; }
61
62   private:
63      T m_value;
64  };
65
66And we can print any node using the following streaming operator:
67
68  inline std::ostream& operator<<(std::ostream& s, node_base const& n)
69  {
70      n.print(s);
71      return s;
72  }
73
74Our first challenge is to build an appropriate iterator over these
75lists.
76
77[h2 A Basic Iterator Using `iterator_facade`]
78
79We will construct a `node_iterator` class using inheritance from
80`iterator_facade` to implement most of the iterator's operations.
81
82
83  # include "node.hpp"
85
86  class node_iterator
88  {
89     ...
90  };
91
92
93
95
96`iterator_facade` has several template parameters, so we must decide
97what types to use for the arguments. The parameters are `Derived`,
98`Value`, `CategoryOrTraversal`, `Reference`, and `Difference`.
99
100
101[h3 `Derived`]
102
103Because `iterator_facade` is meant to be used with the CRTP
104[Cop95]_ the first parameter is the iterator class name itself,
105`node_iterator`.
106
107[h3 `Value`]
108
109The `Value` parameter determines the `node_iterator`\ 's
110`value_type`.  In this case, we are iterating over `node_base`
111objects, so `Value` will be `node_base`.
112
113
114[h3 `CategoryOrTraversal`]
115
116Now we have to determine which `iterator traversal concept`_ our
117`node_iterator` is going to model.  Singly-linked lists only have
118forward links, so our iterator can't can't be a `bidirectional
119traversal iterator`_.  Our iterator should be able to make multiple
120passes over the same linked list (unlike, say, an
121`istream_iterator` which consumes the stream it traverses), so it
122must be a `forward traversal iterator`_.  Therefore, we'll pass
123`boost::forward_traversal_tag` in this position [#category]_.
124
125.. [#category] `iterator_facade` also supports old-style category
126   tags, so we could have passed `std::forward_iterator_tag` here;
127   either way, the resulting iterator's `iterator_category` will
128   end up being `std::forward_iterator_tag`.
129
130[h3 `Reference`]
131
132The `Reference` argument becomes the type returned by
133`node_iterator`\ 's dereference operation, and will also be the
134same as `std::iterator_traits<node_iterator>::reference`.  The
135library's default for this parameter is `Value&`; since
136`node_base&` is a good choice for the iterator's `reference`
137type, we can omit this argument, or pass `use_default`.
138
139[h3 `Difference`]
140
141The `Difference` argument determines how the distance between
142two `node_iterator`\ s will be measured and will also be the
143same as `std::iterator_traits<node_iterator>::difference_type`.
144The library's default for `Difference` is `std::ptrdiff_t`, an
145appropriate type for measuring the distance between any two
146addresses in memory, and one that works for almost any iterator,
147so we can omit this argument, too.
148
149The declaration of `node_iterator` will therefore look something
150like:
151
152  # include "node.hpp"
154
155  class node_iterator
157          node_iterator
158        , node_base
159        , boost::forward_traversal_tag
160      >
161  {
162     ...
163  };
164
165
166[h2 Constructors and Data Members]
167
168Next we need to decide how to represent the iterator's position.
169This representation will take the form of data members, so we'll
170also need to write constructors to initialize them.  The
171`node_iterator`\ 's position is quite naturally represented using
172a pointer to a `node_base`.  We'll need a constructor to build an
173iterator from a `node_base*`, and a default constructor to
174satisfy the `forward traversal iterator`_ requirements [#default]_.
175Our `node_iterator` then becomes:
176
177  # include "node.hpp"
179
180  class node_iterator
182          node_iterator
183        , node_base
184        , boost::forward_traversal_tag
185      >
186  {
187   public:
188      node_iterator()
189        : m_node(0)
190      {}
191
192      explicit node_iterator(node_base* p)
193        : m_node(p)
194      {}
195
196   private:
197      ...
198      node_base* m_node;
199  };
200
201.. [#default] Technically, the C++ standard places almost no
202   requirements on a default-constructed iterator, so if we were
203   really concerned with efficiency, we could've written the
204   default constructor to leave `m_node` uninitialized.
205
206[h2 Implementing the Core Operations]
207
208The last step is to implement the `core operations`_ required by
209the concepts we want our iterator to model.  Referring to the
210table__, we can see that the first three rows are applicable
211because `node_iterator` needs to satisfy the requirements for
212`readable iterator`_, `single pass iterator`_, and `incrementable
213iterator`_.
214
215__ `core operations`_
216
217We therefore need to supply `dereference`,
218`equal`, and `increment` members.  We don't want these members
219to become part of `node_iterator`\ 's public interface, so we can
220make them private and grant friendship to
221`boost::iterator_core_access`, a "back-door" that
223
224  # include "node.hpp"
226
227  class node_iterator
229          node_iterator
230        , node_base
231        , boost::forward_traversal_tag
232      >
233  {
234   public:
235      node_iterator()
236        : m_node(0) {}
237
238      explicit node_iterator(node_base* p)
239        : m_node(p) {}
240
241   private:
242      friend class boost::iterator_core_access;
243
244      void increment() { m_node = m_node->next(); }
245
246      bool equal(node_iterator const& other) const
247      {
248          return this->m_node == other.m_node;
249      }
250
251      node_base& dereference() const { return *m_node; }
252
253      node_base* m_node;
254  };
255
256Voila; a complete and conforming readable, forward-traversal
257iterator!  For a working example of its use, see
258[@../example/node_iterator1.cpp `this program`].
259
260__ ../example/node_iterator1.cpp
261
262[h2 A constant `node_iterator`]
263
264[blurb *Constant and Mutable iterators*\n\n
265The term **mutable iterator** means an iterator through which
266the object it references (its "referent") can be modified.  A
267**constant iterator** is one which doesn't allow modification of
268its referent.\n\n
269The words *constant* and *mutable* don't refer to the ability to
270modify the iterator itself.  For example, an `int const*` is a
271non-\ `const` *constant iterator*, which can be incremented
272but doesn't allow modification of its referent, and `int*
273const` is a `const` *mutable iterator*, which cannot be
274modified but which allows modification of its referent.\n\n
275Confusing?  We agree, but those are the standard terms.  It
276probably doesn't help much that a container's constant iterator
277is called `const_iterator`.
278]
279
281's `print(std::ostream&) const` member function, but also its
282mutating `double_me()` member.  If we wanted to build a
283*constant* `node_iterator`, we'd only have to make three
284changes:
285
286  class const_node_iterator
288          node_iterator
289        , node_base **const**
290        , boost::forward_traversal_tag
291      >
292  {
293   public:
294      const_node_iterator()
295        : m_node(0) {}
296
297      explicit const_node_iterator(node_base* p)
298        : m_node(p) {}
299
300   private:
301      friend class boost::iterator_core_access;
302
303      void increment() { m_node = m_node->next(); }
304
305      bool equal(const_node_iterator const& other) const
306      {
307          return this->m_node == other.m_node;
308      }
309
310      node_base **const**\ & dereference() const { return \*m_node; }
311
312      node_base **const**\ * m_node;
313  };
314
315[blurb `const` and an iterator's `value_type`\n\n
316The C++ standard requires an iterator's `value_type` *not* be
317`const`\ -qualified, so `iterator_facade` strips the
318`const` from its `Value` parameter in order to produce the
319iterator's `value_type`.  Making the `Value` argument
320`const` provides a useful hint to `iterator_facade` that the
321iterator is a *constant iterator*, and the default `Reference`
322argument will be correct for all lvalue iterators.
323]
324
325As a matter of fact, `node_iterator` and `const_node_iterator`
326are so similar that it makes sense to factor the common code out
327into a template as follows:
328
329  template <class Value>
330  class node_iter
332          node_iter<Value>
333        , Value
334        , boost::forward_traversal_tag
335      >
336  {
337   public:
338      node_iter()
339        : m_node(0) {}
340
341      explicit node_iter(Value* p)
342        : m_node(p) {}
343
344   private:
345      friend class boost::iterator_core_access;
346
347      bool equal(node_iter<Value> const& other) const
348      {
349          return this->m_node == other.m_node;
350      }
351
352      void increment()
353      { m_node = m_node->next(); }
354
355      Value& dereference() const
356      { return *m_node; }
357
358      Value* m_node;
359  };
360  typedef node_iter<node_base> node_iterator;
361  typedef node_iter<node_base const> node_const_iterator;
362
363
364[h2 Interoperability]
365
366Our `const_node_iterator` works perfectly well on its own, but
367taken together with `node_iterator` it doesn't quite meet
368expectations.  For example, we'd like to be able to pass a
369`node_iterator` where a `node_const_iterator` was expected,
370just as you can with `std::list<int>`\ 's `iterator` and
371`const_iterator`.  Furthermore, given a `node_iterator` and a
372`node_const_iterator` into the same list, we should be able to
373compare them for equality.
374
375This expected ability to use two different iterator types together
376is known as |interoperability|_.  Achieving interoperability in
377our case is as simple as templatizing the `equal` function and
378adding a templatized converting constructor [#broken]_ [#random]_:
379
380  template <class Value>
381  class node_iter
383          node_iter<Value>
384        , Value
385        , boost::forward_traversal_tag
386      >
387  {
388   public:
389      node_iter()
390        : m_node(0) {}
391
392      explicit node_iter(Value* p)
393        : m_node(p) {}
394
395      template <class OtherValue>
396      node_iter(node_iter<OtherValue> const& other)
397        : m_node(other.m_node) {}
398
399   private:
400      friend class boost::iterator_core_access;
401      template <class> friend class node_iter;
402
403      template <class OtherValue>
404      bool equal(node_iter<OtherValue> const& other) const
405      {
406          return this->m_node == other.m_node;
407      }
408
409      void increment()
410      { m_node = m_node->next(); }
411
412      Value& dereference() const
413      { return *m_node; }
414
415      Value* m_node;
416  };
417  typedef impl::node_iterator<node_base> node_iterator;
418  typedef impl::node_iterator<node_base const> node_const_iterator;
419
420.. |interoperability| replace:: **interoperability**
421.. _interoperability: new-iter-concepts.html#interoperable-iterators-lib-interoperable-iterators
422
423.. [#broken] If you're using an older compiler and it can't handle
424   this example, see the `example code`__ for workarounds.
425
426.. [#random] If `node_iterator` had been a `random access
427   traversal iterator`_, we'd have had to templatize its
428   `distance_to` function as well.
429
430
431__ ../example/node_iterator2.hpp
432
433You can see an example program which exercises our interoperable
434iterators
435[@../example/node_iterator2.cpp `here`].
436
437
438[h2 Telling the Truth]
439
440Now `node_iterator` and `node_const_iterator` behave exactly as
441you'd expect... almost.  We can compare them and we can convert in
442one direction: from `node_iterator` to `node_const_iterator`.
443If we try to convert from `node_const_iterator` to
444`node_iterator`, we'll get an error when the converting
445constructor tries to initialize `node_iterator`\ 's `m_node`, a
446`node*` with a `node const*`.  So what's the problem?
447
448The problem is that
449`boost::`\ |is_convertible|_\ `<node_const_iterator,node_iterator>::value`
450will be `true`, but it should be `false`.  |is_convertible|_
451lies because it can only see as far as the *declaration* of
452`node_iter`\ 's converting constructor, but can't look inside at
453the *definition* to make sure it will compile.  A perfect solution
454would make `node_iter`\ 's converting constructor disappear when
455the `m_node` conversion would fail.
456
457.. |is_convertible| replace:: `is_convertible`
458.. _is_convertible:  ../../type_traits/index.html#relationships
459
460In fact, that sort of magic is possible using
461|enable_if|__.  By rewriting the converting constructor as
462follows, we can remove it from the overload set when it's not
463appropriate:
464
465  #include <boost/type_traits/is_convertible.hpp>
466  #include <boost/utility/enable_if.hpp>
467
468    ...
469
470  private:
471    struct enabler {};
472
473  public:
474    template <class OtherValue>
475    node_iter(
476        node_iter<OtherValue> const& other
477      , typename boost::enable_if<
478            boost::is_convertible<OtherValue*,Value*>
479          , enabler
480        >::type = enabler()
481    )
482      : m_node(other.m_node) {}
483
484.. |enable_if| replace:: `boost::enable_if`
485__ ../../utility/enable_if.html
486
487
488[h2 Wrap Up]
489
490This concludes our `iterator_facade` tutorial, but before you
492There's another way to approach writing these iterators which might
493even be superior.
494