1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95
|
Concepts can be nested. Being able to nest concepts is very useful as it
allows us to hierarchically order concepts and to define concepts in terms of
existing concepts.
In chapter ref(STL) iterators were introduced (section
ref(ITERATORS)). Commonly five conceptually different iterator types are
distinguished:
itemization(
it() Input iterators are incrementable, and they support dereferencing to
const values;
it() Output iterators are like Input iterators, but they refer to
non-const values;
it() Forward iterators combine Input and Output iterators;
it() Bidirectional iterators are like Forward iterators, but
they also support decrement operators;
it() RandomAccess iterators are like Bidirectional iterators, but these
iterators also support addition and subtraction by any stepsize.
)
figure(advancedtemplates/concepts/nested)(Concept Hierarchy)(ConceptFig)
All iterator types support (in)equality checks and increment operators. Thus,
at the basis of all iterators we find the requirements that iterators must be
comparable and incrementable. Concepts covering those requirements are easily
constructed (see also figure ref(ConceptFig)):
verbinsert(-s4 //comparable examples/nested.cc)
Note that no type is specified following the tt(lhs == rhs) and tt(lhs !=
rhs) requirements, as those types are implied by their operators.
Two more concepts are defined: one allowing dereferencing pointers
returning constant references and one returning modifiable references. To
allow the compiler to verify those requirements we also implicitly require the
(commonly encountered) existence of typename tt(Type::value_type):
verbinsert(-s4 //dereference examples/nested.cc)
verbinsert(-as4 examples/constderef.cc)
Not much of a hierarchy so far, but that changes now that we're about to
define concepts for iterators.
An input iterator is an iterator that is comparable, incrementable and
const-dereferenceable. For each of these requirements concepts were defined
which can be combined using boolean operators when defining the concept
tt(InIterator). Note that template type parameters of concepts em(must) use
the tt(typename) keyword. Concepts'
hi(concept: constraints) template parameters cannot be constrained by
specifying them in terms of existing concepts (which em(is) possible when
defining function and class templates).
Here is the definition of the concept tt(InIterator). The function template
tt(inFun) (below the concept tt(InIterator)) illustrates how a
constrained template parameter type can be specified in template headers:
verbinsert(-s4 //initerator examples/nested.cc)
The concept for output iterators (and its use, as in the function
template tt(outFun)) is defined analogously. This time requiring
dereferenceable types rather than const-dereferenceable types:
verbinsert(-s4 //outiterator examples/nested.cc)
For forward iterators the concept tt(FwdIterator) is defined. A forward
iterator combines the characteristics of input and output iterators, and we
may want to define a forward iterator by requiring the requirements of the
tt(InIterator) and tt(OutIterator) concepts.
However, there's a slight problem. The following class (struct) defines const
and non-const dereference operators and may be therefore be passed to
functions expecting input or output iterators:
verbinsert(-s4 //iterable examples/nested.cc)
But when a function template requires tt(ConstDerefenceable) arguments then
the compiler notices that the overloaded member tt(int &operator*()) doesn't
return an tt(int const &). Even though tt(int const &operator*() const) is
available compilation fails. This problem can be solved in two ways: noting
that an tt(int &) can be converted to an tt(int const &) the predefined
concept tt(std::convertible_to) instead of tt(std::same_as) can be used in
tt(ConstDereferenceable); alternatively its tt(requires) clause can specify
tt(Type const &type) instead of just tt(Type type). Here is a definition of
tt(ConstDereferenceable) that, when defining the concept tt(FwdIter), can be
used in combination with tt(Dereferenceable):
verbinsert(-s4 //constderef examples/nested.cc)
COMMENT(
verbinsert(-s4 //fwditerator examples/nested.cc)
END)
The final two iterator types pose no problems: the concept tt(BiIterator)
requires the constraints of the concept tt(FwdIterator) as well as decrement
operators, and finally the concept tt(RndIterator) requires the constraints of
tt(BiIterator) and in addition iterator increments decrements for any step
size as well as the possibility to subtract iterators:
verbinsert(-s4 //bidir examples/nested.cc)
|