41

Iterators of the categories forward, bidirectional, and random access need to be default-constructible.

Why is this, and why do input and output operators not have to be default-constructible?

sbi
  • 219,715
  • 46
  • 258
  • 445
  • 3
    Probably because many used to write `std::vector::iterator it;` outside of a for loop to avoid going over the recommended 78 characters line width. If you were to use `std::vector::iterator it = container.begin();` then the for loop would not be as sexy: `for (; it != container.end(); ++it)`. – Shoe Mar 03 '15 at 13:13
  • 2
    Unfortunately some algorithms are implemented in such a way to require default-constructible. This seems to be in principle a reasonable requirement to avoid premature assignment but in the examples I saw it can be avoided easily. I recently tried to implement an iterator and requiring default-constructible unfortunately added a level of indirection that was unnecessary otherwise. So I ended up paying for the extra level of indirection *all the time*, I think this requirement is very unfortunate because it has a nasty hidden cost. – alfC Jun 12 '18 at 20:40

3 Answers3

18

Forward iterators and stronger are required to refer to some external sequence (see [forward.iterators]/6 which says "If a and b are both dereferenceable, then a == b if and only if *a and *b are bound to the same object.")

This means they are generally just a lightweight handle onto something else (e.g. a pointer to an element or a node in a container) and so there is little reason not to require that they can be default constructed (even if default construction creates a singular iterator that can't be used for anything until assigned a new value). It's possible for all non-pathological* forward iterators to support default construction, and relying on that makes some algorithms easier to implement.

Iterators which only meet the input iterator or output iterator requirements (and nothing stronger) might contain state within themselves which is modified by operator++ and so it might not be possible for that state to be default-constructed. No algorithm that only operates on input/output iterators needs to default construct them, so it isn't required.

  • spot the "no true scotsman" argument here ;)
Jonathan Wakely
  • 166,810
  • 27
  • 341
  • 521
  • 8
    The standard doesn't generally specify something just because there's no reason not to; they generally have a very good reason. – Mark Ransom Mar 03 '15 at 13:21
  • 2
    To me this seems very STL container-specific. It's easy to think of some custom iterator that stores more state than a `std::vector<>::iterator` and where that state cannot be created out of nothing either. And why shouldn't input and output iterator be singular? (In fact, stream iterators are default-constructible.) – sbi Mar 03 '15 at 13:27
  • @MarkRansom Note that the SGI STL [says](https://www.sgi.com/tech/stl/trivial.html) all iterators must be DefaultConstructible, the standard actually removed that requirement for input and output iterators. – Jonathan Wakely Mar 03 '15 at 13:27
  • @sbi default-constructed stream iterators are not singular, they are the past-the-end value. That is a special case, and not all input iterators are required to do that, there may not be an obvious singular state for all types. And iterators are welcome to store more state, but the thing that `operator*` refers to cannot be stored in the iterator itself, so the thing you're really interested in is external to the iterator. – Jonathan Wakely Mar 03 '15 at 13:28
  • @MarkRansom, or look at it the other way round. There is no good reason to _require_ that input iterators be default constructible (no algorithm on InputIterators requires it) so it is not required. There are a number of STL algorithms that use a default-constructed iterator, but they only operate on ForwardIterators or stronger (it might be possible to rewrite those algorithms to not require it, I haven't checked how easy that would be, maybe the committee did so in the '90s and decided it was not worth it). – Jonathan Wakely Mar 03 '15 at 13:36
  • 11
    In libstdc++ (and so probably also in the SGI STL) at least `std::search`, `std::partition_point` and `std::minmax_element` rely on default-constructing a forward iterator. `std::rotate` default constructs a bidirectional iterator. In theory they could be turned into redundant copies (they will be assigned new values later anyway). – Jonathan Wakely Mar 03 '15 at 13:58
  • In fact, when I was asked this question by a colleague, my immediate gut reaction was "I bet there's algorithms that need default-constructible iterators." Then this would boil down to outweighing the advantage of having a dctor available against the need to semantically define what it does for every iterator category. That would make sense to me. However (and this is by no means criticizing your answer), I was fishing for something less vague and more substantial here. _What (class of) algorithms would need iterators with dctors and won't work on i/o iterators?_ – sbi Mar 03 '15 at 13:58
  • @Jonathan: Oh, there's the examples! 10secs before I hit enter on my comment asking for them. `:) ` – sbi Mar 03 '15 at 14:00
  • 3
    I suspect that it happened the other way around, i.e. rather than intentionally requiring it only for forward and better it was "un-required" for input/output. The SGI STL authors, and maybe Stepanov too, relied on DefaultConstructible for all iterators, as generalisations of pointers. At some point during standardisation someone realised that no algorithms operating in input or output iterators actually needed default construction, so the requirement was dropped for those categories. Maybe it could also have been dropped for forward and bidirectional, if anyone had bothered to do the work. – Jonathan Wakely Mar 03 '15 at 14:01
  • 1
    A true Scotsman only uses raw pointers. – Hunter Kohler Mar 10 '22 at 20:16
4

reference for iterators

Input/Output iterators:

Input Iterator: once an InputIterator i has been incremented, all copies of its previous value may be invalidated.

Output Iterator: After this operation r is not required to be dereferenceable and any copies of the previous value of r are no longer required to be dereferenceable or incrementable.

If we look at this it seems clear that these iterators are designed to be used in the simplest method possible. Much like an array index or simple pointer would be used for single-pass algorithms. There is therefore really no need to have default constructors.

Note however that just because default constructors is not required technically doesn't disqualify you from implementing them if you want.

Forward iterators:

These are the first level of iterators that require default constructors but why?

There are many reasons related to historical programming reasons ect and I believe this is all to some extent valid. In a certain way I think the committee figured somewhere between iterator and randomAccessIterator default construction was required to be implemented and forward iterators seemed like the best choice.

There is however one fairly good reason why:

Forward iterators support multi-pass algorithms and therefore require that copies of the iterator are still valid after the iterator has been used/incremented. If these copies are valid still it means the algorithm would allow us to "save" them somewhere. Which also means the iterator where we save them needs to have a default/initial value.

Consider:

class MyClass
{
 public:
  void myFunction(ForwardIterator &i)
  {
    //do some code here
    savedIter = i;
    //do some code here
  }
 private:
  ForwardIterator savedIter;
}

By definition this is valid because we are allowed to save the iterator for some amount of time as the requirement is that copies of this iterator will remain valid. (At least until the data structure the iterator points to is destroyed)

However for this class to be created ForwardIterator requires a default constructor obviously...

  • 3
    You could save just as well copy-constructed iterators, couldn't you? All in all, there's a lot of hand waving in this answer. – sbi Mar 03 '15 at 21:47
  • Can you please explain how you would create a "placeholder" in which to save iterators without a default constructor in an elegant way? Also english is not my first language so I'm not 100% sure to which part you refer about "hand waving" can you please explain so I can try to improve the answer. – Heinrich du Toit Mar 04 '15 at 06:28
  • In my experience, hand waving often occurs where quotation marks are used, or words like "obviously". – DaveFar Jan 05 '19 at 13:31
3

Forward / bidirectional / random access iterators can often be pointers - it would historically have aided migration from pointer-using code to iterators if the construction and initialisation could be left delocalised if that was the way the code happened to be. Forcing more wholesale change would have frustrated a lot of people trying to migrate older code off explicit use of pointers and onto iterators. Changing it would now break a lot of code.

Input and output operators are often most elegantly implemented with references to underlying stream or other I/O objects, and references must be initialised at construction. Of course, implementations could be forced to defer that, and use pointers internally, but that's sure to rub some people up the wrong way - seeming too "C"-like - so it's unsurprising the Standard facilitates use of references.

Tony Delroy
  • 102,968
  • 15
  • 177
  • 252
  • This is more of an argument why they must be assignable. `std::istream_iterator` is default-constructible and the end-of-stream marker. – MSalters Mar 03 '15 at 13:31
  • @MSalters: `std::istream_iterator` had a very specific use for default construction (as a sentinel), so it could prioritorise that and accept whatever implementation compromises that required, but the Standard's not requiring all input/output iterators to be default constructible still lets individual implementations make what they consider the most elegant choice. – Tony Delroy Mar 03 '15 at 13:38
  • I think this answer spots the dilemma, DefaultConstructible is a nice requirement, but forces you to implement things in terms of pointers, even if no pointers needed to be involved a priori, and penalizes an more elegant implementation in terms of references. – alfC May 11 '17 at 22:28