83

On March 21st the standards committee voted to approve the deprecation of std::iterator proposed in P0174:

The long sequence of void arguments is much less clear to the reader than simply providing the expected typedefs in the class definition itself, which is the approach taken by the current working draft, following the pattern set in

Before inheritance from std::iterator was encouraged to remove the tedium from iterator boilerplate implementation. But the deprecation will require one of these things:

  1. An iterator boilerplate will now need to include all required typedefs
  2. Algorithms working with iterators will now need to use auto rather than depending upon the iterator to declare types
  3. Loki Astari has suggested that std::iterator_traits may be updated to work without inheriting from std::iterator

Can someone enlighten me on which of these options I should expect, as I design custom iterators with an eye towards compatibility?

Jonathan Mee
  • 37,899
  • 23
  • 129
  • 288
  • 8
    @FirstStep I would hope to get an answer that would not be opinion based. If the standard committee is deprecating a class I depend on next year I'd hope they'd have a direction they are channeling me towards right now. – Jonathan Mee May 04 '16 at 15:20
  • 2
    Just because they are deprecating it does not mean you can't keep using it for a while. – Jesper Juhl May 04 '16 at 15:24
  • Note: `deprecating` means it is no longer recommended but it will still be supported for at least another version of the standard. – Martin York May 04 '16 at 15:28
  • 4
    The iterators in the standard library have gone for option 1. – Bo Persson May 04 '16 at 15:29
  • @BoPersson It would seem to indicate that we should do this as well then? – Jonathan Mee May 04 '16 at 15:37
  • 1
    @LokiAstari - it's even weaker than that. Formally, deprecation is a notice that something might go away in the future. That's all. Note that the standard C headers have been deprecated in C++ since 1998. – Pete Becker May 04 '16 at 15:46
  • @PeteBecker But still this *is* the standard committee trying to steer us away from these things right. I mean I've used `#include ` as long as I've been writing C++ on account of this. Don't tell me that's been in vain! – Jonathan Mee May 04 '16 at 15:50
  • 1
    @JonathanMee - no, it hasn't been in vain. It hasn't been necessary, but there are advantages to it. – Pete Becker May 04 '16 at 15:51
  • @PeteBecker And they're saying something similar here right? You've used this for a long time but there's a better way. Hence the question... – Jonathan Mee May 04 '16 at 15:53
  • 1
    Option 1. A thing isn't an iterator unless `iterator_traits` for it has the various member typedefs, and there's no point in specializing `iterator_traits` - it's more typing. – T.C. May 04 '16 at 15:58
  • 2
    @JonathanMee - I use `std::iterator` because it's convenient. I'll continue to use it until I can't. – Pete Becker May 04 '16 at 16:06
  • 1
    @PeteBecker I respect that, but if the standard committee has decided it's time for us to move on I'm ready to start making the transition. – Jonathan Mee May 04 '16 at 16:15
  • 1
    @PeteBecker: I was wondering it the template specialization of iterator_traits can use inheritance to reduce typing? Would that be worth the effort if it reduced boilerplate? – Martin York May 04 '16 at 17:20
  • @LokiAstari Yes, undefined behavior includes "appearing to work". – T.C. May 04 '16 at 17:36
  • @LokiAstari ["Accordingly, it is required that if Iterator is the type of an iterator, \[the `iterator_traits` typedefs be defined\]."](http://eel.is/c++draft/iterator.traits#1) – T.C. May 04 '16 at 17:48
  • @T.C.: Thanks. Worth the reference. :-) – Martin York May 04 '16 at 17:58

2 Answers2

79

The discussed alternatives are clear but I feel that a code example is needed.

Given that there will not be a language substitute and without relying on boost or on your own version of iterator base class, the following code that uses std::iterator will be fixed to the code underneath.

With std::iterator

template<long FROM, long TO>
class Range {
public:
    // member typedefs provided through inheriting from std::iterator
    class iterator: public std::iterator<
                        std::forward_iterator_tag, // iterator_category
                        long,                      // value_type
                        long,                      // difference_type
                        const long*,               // pointer
                        const long&                // reference
                                      > {
        long num = FROM;
    public:
        iterator(long _num = 0) : num(_num) {}
        iterator& operator++() {num = TO >= FROM ? num + 1: num - 1; return *this;}
        iterator operator++(int) {iterator retval = *this; ++(*this); return retval;}
        bool operator==(iterator other) const {return num == other.num;}
        bool operator!=(iterator other) const {return !(*this == other);}
        long operator*() {return num;}
    };
    iterator begin() {return FROM;}
    iterator end() {return TO >= FROM? TO+1 : TO-1;}
};

(Code from http://en.cppreference.com/w/cpp/iterator/iterator with original author's permission).

Without std::iterator

template<long FROM, long TO>
class Range {
public:
    class iterator {
        long num = FROM;
    public:
        iterator(long _num = 0) : num(_num) {}
        iterator& operator++() {num = TO >= FROM ? num + 1: num - 1; return *this;}
        iterator operator++(int) {iterator retval = *this; ++(*this); return retval;}
        bool operator==(iterator other) const {return num == other.num;}
        bool operator!=(iterator other) const {return !(*this == other);}
        long operator*() {return num;}
        // iterator traits
        using difference_type = long;
        using value_type = long;
        using pointer = const long*;
        using reference = const long&;
        using iterator_category = std::forward_iterator_tag;
    };
    iterator begin() {return FROM;}
    iterator end() {return TO >= FROM? TO+1 : TO-1;}
};
Armen Michaeli
  • 8,625
  • 8
  • 58
  • 95
Amir Kirsh
  • 12,564
  • 41
  • 74
  • 4
    @AmirKirsh `operator*` should return `reference`, you need an `operator->` returning a `pointer` even though it doesn't make sense for a `long` – Ryan Haining Nov 17 '16 at 16:27
  • 5
    I found a pretty fantastic article about it [here](https://www.fluentcpp.com/2018/05/08/std-iterator-deprecated/), too, that outlines the justifications. – Jason C Dec 17 '20 at 21:04
33

Option 3 is a strictly more-typing version of Option 1, since you have to write all the same typedefs but additionally wrap iterator_traits<X>.

Option 2 is unviable as a solution. You can deduce some types (e.g. reference is just decltype(*it)), but you cannot deduce iterator_category. You cannot differentiate between input_iterator_tag and forward_iterator_tag simply by presence of operations since you cannot reflexively check if the iterator satisfies the multipass guarantee. Additionally, you cannot really distinguish between those and output_iterator_tag if the iterator yields a mutable reference. They will have to be explicitly provided somewhere.

That leaves Option 1. Guess we should just get used to writing all the boilerplate. I, for one, welcome our new carpal-tunnel overlords.

Barry
  • 286,269
  • 29
  • 621
  • 977
  • 3
    If you really like what `std::iterator` does, you can trivially write your own version. So the risk of carpal-tunnel is highly overblown. – T.C. May 04 '16 at 18:27
  • @Barry It seems that iterator_tag is the crux, but that's a bit frustrating, since it could be determined by the presence or absence of, const and index and decrement operators, right? – Jonathan Mee May 04 '16 at 18:29
  • @JonathanMee Nope. Is this an input or output iterator? – Yakk - Adam Nevraumont May 04 '16 at 19:35
  • Barry, option 3 could be viewed as "option 2 but `iterator_traits` does the work for you". It fails on the tag type. – Yakk - Adam Nevraumont May 04 '16 at 19:35
  • @Yakk Can't you determine that from whether it's values return `const`? – Jonathan Mee May 04 '16 at 19:41
  • @JonathanMee You can't tell between input/forward, and a forward iterator is by no means required to give you a `const` reference... – Barry May 04 '16 at 20:24
  • @Yakk A "ForwardIterator" is an "InputIterator" unless the type returned by deferencing it is `const` then it's just an "OutputIterator" not an "InputIterator" – Jonathan Mee May 04 '16 at 20:29
  • Both input and output do not have to return values or references, if I remember that quirky corner case. But I guess that is a corner case. – Yakk - Adam Nevraumont May 04 '16 at 20:31
  • 5
    @JonathanMee That doesn't make any sense. – Barry May 04 '16 at 20:31
  • @Barry Wait, how does that not make sense? If the iterator class doesn't provide `T& operator*()`, only `const T& operator*() const` then this is an "OutputIterator", it's an "InputIterator". – Jonathan Mee May 05 '16 at 10:53
  • @Jonathan output iterators can't dereference to a `const T&` - they have to be writeable to. They must support `*it++ = x;` – Barry May 05 '16 at 12:06
  • @Barry Sooo... why can't we key off that? – Jonathan Mee May 05 '16 at 12:12
  • @Jonathan Off of what? Input Iterator and Forward Iterator can dereference to a non-const lvalue reference too. – Barry May 05 '16 at 12:24
  • @Barry Note that an "OutputIterator" is an "InputIterator" as well, as long as the returns of the "InputIterator" dereferences are non-`const`. That's what I'm keying off of. – Jonathan Mee May 05 '16 at 12:36
  • @Jonathan An Output Iterator does not have to be an Input Iterator. – Barry May 05 '16 at 12:40
  • Mee and http://en.cppreference.com thought it did: http://en.cppreference.com/w/cpp/iterator Do you have a counter example? – Jonathan Mee May 05 '16 at 12:44
  • @Jonathan Nowhere does that reference claim that an Output Iterator is an Input Iterator. – Barry May 05 '16 at 13:18
  • "An OutputIterator is an Iterator that can write to the pointed-to element." Help me out here. You say, "An Output Iterator does not have to be an Input Iterator." Give me an example. – Jonathan Mee May 05 '16 at 13:28
  • 7
    @JonathanMee Dude. "Iterator" and "Input Iterator" are **not equivalent**. – Barry May 05 '16 at 13:36
  • not much carpel-tunnel tribute required if you use `boost::iterator_facade` or `boost::iterator_adaptor` – TemplateRex May 11 '16 at 15:39
  • 11
    @TemplateRex It was a joke. Regardless, it seems silly to deprecate `std::iterator` in favor of... everyone now writing their own copy of `std::iterator` to solve that problem anyway. – Barry May 11 '16 at 17:50
  • 4
    related: http://stackoverflow.com/q/29108958/819272 It's a more general trend in the Standard to remove silly base classes only containing typedefs (unary_function etc.) – TemplateRex May 11 '16 at 20:28
  • @JonathanMee The iterator might return a const proxy object through which you still can manipulate the underlying container. Thus const isn't a reliable indicator for input/forward iterators. –  May 16 '16 at 00:58
  • What about using `boost::iterator_facade` ? – alfC Oct 10 '16 at 23:26