5.2.21 · D5C++ Programming
Question bank — Iterators — input, output, forward, bidirectional, random access, contiguous
This bank targets the boundary cases the containers and algorithms invite you to confuse.
True or false — justify
True or false: every random-access iterator is also a bidirectional iterator.
True. The categories form a refinement chain, so random access is a strict superset — it keeps
--it and adds it+n, it[n], etc. on top.True or false: every bidirectional iterator is also a random-access iterator.
False. Refinement only goes one way. A
std::list iterator can step --it but cannot jump it+n in , so it stops at bidirectional.True or false: std::deque iterators support it[5] in .
True.
deque is random-access, so subscripting is constant-time — it just isn't contiguous, which is a separate promise about memory layout.True or false: because deque supports it[5], you may pass &dq[0] to a C function expecting a pointer to a run of elements.
False. Random access guarantees fast indexing, not that element 5 and element 6 share one memory block. Only contiguous iterators promise
&*(it+n) == &*it + n.True or false: an ostream_iterator can be compared with == to detect the end.
False. Pure output iterators have no equality and no end sentinel; you must drive them from a known-length source range instead.
True or false: copying an input iterator and advancing the copy leaves the original still valid.
False. Input iterators are single-pass — advancing any copy consumes the underlying stream, so the other copy is invalidated. Multi-pass safety begins at forward.
True or false: a forward iterator supports both reading and writing through *it.
True. Forward iterators are multi-pass and mutable (unless the container is const), so
x = *it and *it = x are both allowed — that's what separates them from input/output.True or false: std::sort works on std::list.
False.
std::sort needs random-access iterators to partition by jumping around; list only has bidirectional iterators, which is exactly why list ships its own L.sort() member.True or false: contiguous is a C++20 refinement that adds no new operations over random access.
True. Contiguous adds a memory guarantee (
&*(it+n) == &*it + n), not a new callable operation. The operations are identical to random access.True or false: std::string iterators are contiguous.
True. A
string stores its characters in one contiguous block (like vector and array), so &*(s.begin()+n) == &*s.begin() + n holds.Spot the error
auto m = L.begin() + 2; where L is a std::list<int>.
list iterators are bidirectional, so operator + does not exist — this fails to compile. Use std::advance(m, 2) (an loop of ++).std::istream_iterator<int> in(cin), end; auto x = *in; ++in; auto y = *in; /* now reuse a stored copy of the first position */.
Storing and revisiting the first read position is illegal for input iterators — they are single-pass, so the old position is invalid the moment you advance.
std::copy(v.begin(), v.end(), ostream_iterator<int>(cout, " ")); if (dest == somewhere) ... where dest is the output iterator.
You cannot compare a pure output iterator with
==; output iterators have no equality. Track how many elements you wrote instead.Passing a back_insert_iterator where an algorithm reads with *it as a source.
back_inserter is output-only. Reading through it (x = *it) is not supported; it only supports *it = value and ++it.double* p = &dq[0]; use_c_api(p, dq.size()); for a std::deque<double> dq.
deque is not contiguous, so p+1 may not point at element 1 — the memory is split into chunks. Only vector/array/string may be handed to such a C API.Calling std::advance(it, -3) on an istream_iterator.
Input iterators move forward only; negative advance would require
--it, which they lack. Negative distances are legal only from bidirectional upward.Why questions
Why is there a hierarchy of categories instead of one universal iterator type?
Because not every container can perform every operation cheaply — a list cannot jump in . Categories encode which operations are affordable so the compiler picks the fastest valid algorithm.
Why does std::advance cost for a vector but for a list?
Tag dispatch selects
it += n for random-access iterators (one jump) and a ++it loop for bidirectional ones, because only random-access memory supports a constant-time jump.Why is the tag a distinct empty struct type rather than a runtime enum?
Its type selects the overload at compile time, so the dispatch has zero runtime cost — the branch is resolved before the program runs. See Templates and Tag Dispatch.
Why can std::copy write into a stream using only an output iterator?
copy writes its destination with *it = value and advances with ++it — exactly the output-iterator contract. It never reads the destination, so nothing stronger is needed.Why is contiguous "necessary but not sufficient" phrased the other way for random access?
Random access is necessary for contiguous (you need
it+n first) but not sufficient — you also need the addresses to be linear, which random access alone doesn't promise.Why does interfacing with old C code specifically demand a contiguous iterator?
C APIs expect a raw pointer into one flat array where
p+n is the n-th element. Only the contiguous guarantee &*(it+n) == &*it + n makes &*container.begin() behave that way. Compare with plain Pointers and pointer arithmetic.Edge cases
What category does a raw pointer int* satisfy?
Contiguous — the strongest. A pointer supports read/write,
++, --, +n, subtraction, ordering, and its addresses are by definition linear in memory.What happens with std::advance(it, 0) on any category?
Nothing moves — zero steps is valid for every category, including input, since no direction or jump is actually performed.
Can a forward iterator be the only thing std::find needs?
Yes.
find reads with *it, advances with ++it, and compares positions with != — all supported from input/forward upward, so it works on the widest range of containers.For an empty range where begin() == end(), which categories still let you detect emptiness?
Any category with equality — input, forward, bidirectional, random access, contiguous. Pure output iterators cannot, since they have no
== and no end sentinel.Is std::map's iterator random access because you can look up keys in ?
No. Key lookup is a container operation on the tree; the iterator only walks the sorted sequence with
++/--, so it is bidirectional, not random access.Can you write through a const_iterator from a vector?
No. A
const_iterator allows *it reads but forbids *it = x; the category (contiguous) describes traversal power, while const-ness independently governs mutability.Recall One-line self-test
Name the single feature that separates contiguous from random access.
Answer ::: The memory guarantee &*(it+n) == &*it + n — linear addresses — not any new operation.