mirror of
https://github.com/boostorg/stl_interfaces.git
synced 2025-05-12 14:11:41 +00:00
253 lines
8.0 KiB
Plaintext
253 lines
8.0 KiB
Plaintext
[section Tutorial]
|
|
|
|
[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.next()` ]
|
|
[ `void` ]
|
|
[ Increments `i`. ]
|
|
[ ]
|
|
]
|
|
[
|
|
[ `i.prev()` ]
|
|
[ `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
|
|
next()`` ]
|
|
]
|
|
[
|
|
[ `output_iterator` ]
|
|
[
|
|
``*i
|
|
next()`` ]
|
|
]
|
|
[
|
|
[ `forward_iterator` ]
|
|
[
|
|
``*i
|
|
i == i2
|
|
next()`` ]
|
|
]
|
|
[
|
|
[ `bidirectional_iterator` ]
|
|
[
|
|
``*i
|
|
i == i2
|
|
next()
|
|
prev()`` ]
|
|
]
|
|
[
|
|
[ `random_access_iterator`/`continguous_iterator` ]
|
|
[
|
|
``*i
|
|
i - i2
|
|
i += n`` ]
|
|
]
|
|
]
|
|
|
|
[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_suffix]
|
|
|
|
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're ready to use our forward iterator:
|
|
|
|
[node_iterator_usage]
|
|
|
|
[heading Checking Your Work]
|
|
|
|
_Facade_ 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, _Facade_ 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]
|