stl_interfaces/doc/tutorial.qbk

301 lines
9.8 KiB
Plaintext

[section Tutorial: _iter_iface_]
[heading The _iter_iface_ Template]
Though a given iterator may have a large number of operations associated with
it, there are only a few basis operations that the iterator needs to define;
the full set of operations it supports can be defined in terms of that much
smaller basis.
It is possible to define any iterator `Iter` in terms of a subset of
user-supplied operations. By deriving `Iter` from _iter_iface_ using _CRTP_,
we can generate the full set of operations. Here is the declaration of
_iter_iface_:
template<
typename Derived,
typename IteratorConcept,
typename ValueType,
typename Reference = ValueType &,
typename Pointer = ValueType *,
typename DifferenceType = std::ptrdiff_t>
struct iterator_interface;
Let's break that down.
`Derived` is the type that you're deriving _iter_iface_ from.
`IteratorConcept` defines the iterator category/concept. This must be one of
the C++ standard iterator tag types, like `std::forward_iterator_tag`. In
C++20 and later, `std::contiguous_iterator_tag` is a valid tag to use.
`ValueType` is used to define the iterator's `value_type` typedef. Likewise,
`Reference` and `Pointer` are used to define the iterator's `reference` and
`pointer` typedefs, respectively.
[tip `Reference` does not need to be a reference type, and `Pointer` does not
need to be a pointer type. This fact is very useful when making proxy
iterators. ]
`DifferenceType` is used to define the iterator's `difference_type`. Don't be
a weirdo; just leave this as the default type, `std::ptrdiff_t`.
When defining a proxy iterator, you can use a template alias that provides
reasonable defaults for _iter_iface_'s parameters:
template<
typename Derived,
typename IteratorConcept,
typename ValueType,
typename Reference = ValueType,
typename DifferenceType = std::ptrdiff_t>
using proxy_iterator_interface = iterator_interface<
Derived,
IteratorConcept,
ValueType,
Reference,
proxy_arrow_result<Reference>,
DifferenceType>;
[heading User-Defined Iterator Operations]
Now, let's get back to the user-defined basis operations.
In the table below, `Iter` is a user-defined type derived from _iter_iface_;
`i` and `i2` are objects of type `Iter`; `reference` is the type passed as the
`Reference` template parameter to _iter_iface_; `pointer` is the type passed
as the `Pointer` template parameter to _iter_iface_; and `n` is a value of
type `difference_type`.
[table User-Supplied Operations
[[Expression] [Return Type] [Semantics] [Assertion/note/pre-/post-condition]]
[
[ `*i` ]
[ Convertible to `reference`. ]
[ Dereferences `i` and returns the result. ]
[ ['Expects:] i is dereferenceable. ]
]
[
[ `i == i2` ]
[ Contextually convertible to `bool`. ]
[ Returns true if and only if `i` and `i2` refer to the same value. ]
[ ['Expects:] `(i, i2)` is in the domain of `==`. ]
]
[
[ `i2 - i` ]
[ Convertible to `difference_type`. ]
[ Returns `n`. ]
[ ['Expects:] there exists a value `n` of type `difference_type` such that `i + n == i2`.
`i2 == i + (i2 - i)`. ]
]
[
[ `++i` ]
[ `void` ]
[ Increments `i`. ]
[ ]
]
[
[ `--i` ]
[ `void` ]
[ Decrements `i`. ]
[ ]
]
[
[ `i += n` ]
[ `Iter &` ]
[
`` difference_type m = n;
if (m >= 0)
while (m--) ++i;
else
while (m++) --i;`` ]
[ ]
]
]
[tip The table above leaves a lot of implementation freedom. In
`operator+=()`, you could take `n` as a value or as a reference; `operator-()`
can return a `difference_type` or just something convertible to one; etc. In
particular, your operations can be `constexpr` or `noexcept` as you see fit.]
Not all the iterator concepts require all the operations above. Here are the
operations used with each iterator cocept:
[table Operations Required for Each Concept
[[Concept] [Operations]]
[
[ `input_iterator` ]
[
``*i
i == i2
++i`` ]
]
[
[ `output_iterator` ]
[
``*i
++i`` ]
]
[
[ `forward_iterator` ]
[
``*i
i == i2
++i`` ]
]
[
[ `bidirectional_iterator` ]
[
``*i
i == i2
++i
--i`` ]
]
[
[ `random_access_iterator`/`continguous_iterator` ]
[
``*i
i - i2
i += n`` ]
]
]
[heading An Important Note About `operator++()` and `operator--()`]
There's a wrinkle in this way of doing things. When you define `operator++()`
in your iterator type `Derived`, _iter_iface_ defines post-increment,
`operator++(int)`. But since `Derived` has an `operator++` and so does its
base class _iter_iface_, the one in `Derived` *hides* the one in _iter_iface_.
So, you need to add a using declaration that makes the `operator++` from the
base class visible in the derived class. For instance, in the `node_iterator`
example there are these lines:
[node_iterator_using_declaration]
[important All of the above applies to `operator--`. So, for bidirectional
iterators, you need to add a line like `using base_type::operator--;` as
well. ]
[note These using declarations are not necessary for a random access iterator,
because `Derived` does not have an `operator++()` in that case. ]
[heading Putting it All Together]
Ok, let's actually define a simple iterator. Let's say you need to interact
with some legacy code that has a hand-written linked list:
[node_defn]
We can't change this code to use `std::list`, but it would be nice to be able
to reuse all of the standard algorithms with this type. Defining an iterator
will get us there.
[node_iterator_class_head]
We are deriving `node_iterator` from _iter_iface_, and because we're using
_CRTP_, we first have to pass `node_iterator` for the `Derived` template
parameter, so that _iter_iface_ knows what derived type to cast to in order to
get at the user-defined operations. Then, we pass `std::forward_iterator_tag`
for `IteratorConcept`, since that's appropriate for a singly-linked list.
Finally, we pass `T` to let _iter_iface_ know what the `value_type` is for our
iterator.
We leave the rest of the template parameters at their defaults: `T &` for
`Reference`, `T *` for `Pointer`, and `std::ptrdiff_t` for `DifferenceType`.
This is what you will do for almost all iterators. The most common exceptions
to this are usually some kind of proxy iterator. Another exception is when
for better code generation you want to return builtin values instead of
references for constant iterators. To see an example of the latter, see the
`repeated_chars_iterator` in the introduction; it's `Reference` template
parameter is `char` for this reason.
[node_iterator_ctors]
Next, we define two constructors: a default constructor, and one that takes a
`node` pointer. A default constructor is required by the `forward_iterator`
concept, but _iter_iface_ cannot supply this, since constructors are not
visible in derived types without user intervention.
[important A default constructor is required for every iterator concept.]
[node_iterator_user_ops]
Next, we define the user-defined operations that _iter_iface_ requires to do
its work. As you might expect, the three required operations are very
straightfoward.
[note Here, I implement `operator==()` as a hidden friend function. it would
have worked just as well if I had instead implemanted it as a member function,
like this:
``constexpr bool operator==(node_iterator rhs) const noexcept
{
return it_ == rhs.it_;
}``
Either of these forms works, since _iter_iface_ is concept-based _emdash_ the
appropriate expressions need to be well-formed for the _iter_iface_ tempalte
to do its work. ]
Finally, we need a using declataion to make
`iterator_interface::operator++(int)` visible:
[node_iterator_using_declaration]
Here's how we might use the forward iterator we just defined:
[node_iterator_usage]
[heading Checking Your Work]
_IFaces_ is able to check that some of the code that you write is compatible
with the concept for the iterator you're writing. It cannot check everything.
For instance, _IFaces_ does not know if your derived type includes a default
constructor, which is required by all the iterators. In particular,
_iter_iface_ cannot `static_assert` on the wellformedness of `Derived()`,
since `Derived` is an incomplete type within the body of _iter_iface_
_emdash_ _iter_iface_ is the base class for `Derived`, not the other way
round.
Since you can easily `static_assert` that a type models a given concept, a
good practice is to put such a `static_assert` after you define your iterator
type.
For instance, after `node_iterator` you'll find this code:
[node_iterator_concept_check]
Consider this good code hygiene. Without this simple check, you'll probably
eventually find yourself looking at an error message with a very long template
instantiation stack.
There's also a macro that can help you check that `std::iterator_traits` is
well-formed and provides the corect types. See _traits_m_.
[endsect]
[section Tutorial: _view_iface_]
[heading The _view_iface_ Template]
As with _iter_iface_, _view_iface_ makes it possible to write very few
operations _emdash_ only `begin()` and `end()` are actually used by
_view_iface_ _emdash_ and get all the other operations that go with view
types. The operations added depend on what kinds of iterator and/or sentinel
types your derived view type define.
Here is the declaration of _view_iface_:
template<typename Derived, bool Contiguous = discontiguous>
struct view_interface;
_view_iface_ only requires the derived type, and an optional `bool` non-type
template parameter that indicates whether `Derived`'s iterators are
contiguous. This is necessary to support pre-C++20 code.
TODO
[endsect]