1

Requirements for InputIterator include *i++ with an equivalent expression being

value_type x = *i;
++i;
return x;

How can one declare such an operator without implementing the standard post-increment i++ returning a non-void value (which InputIterators are not required to do)?

quant_dev
  • 6,181
  • 1
  • 34
  • 57
  • 1
    Why would `i++` require `i` to be copyable? Anyway, the iterator being copyable wouldn't be an issue. – juanchopanza Feb 02 '16 at 23:33
  • 1
    If it is some kind of stream iterator, which only produces one value at a time and discards it, you could make it copyable by storing the value in the iterator. Then a copy of the iterator can return the value the input stream had at the time the iterator was copied. – Zan Lynx Feb 02 '16 at 23:36
  • @ZanLynx But that means that `i++` must return a copy of the iterator, not void? As I understand it, InputIterators need only to provide `(void)i++`, not full `i++`. Hence my question. – quant_dev Feb 02 '16 at 23:40
  • Did you mean to ask about what happens to InputIterator when `value_type` is non-copyable? – M.M Feb 02 '16 at 23:41
  • @M.M No. I wanted to ask how to implement `*i++` without implementing full-blown, non-void `i++`. – quant_dev Feb 02 '16 at 23:41
  • 2
    `i++` could return object of some custom type with overloaded `operator*` that yields the desired value – M.M Feb 02 '16 at 23:43
  • There is no way to declare `reference operator*++(int)` or something like that? – quant_dev Feb 02 '16 at 23:44
  • No, it is `operator*` applied to the return value of the iterator's `operator++(int)` – M.M Feb 02 '16 at 23:44
  • OK. Is my understanding correct that the value returned by `i++` could be just a pointer to a private member of `i`, because `*` and `++` are being applied in the same expression? – quant_dev Feb 02 '16 at 23:47
  • It might help people understand what you're looking for if you explain why you want to avoid "implementing full-blown, non-void `i++`" – Michael Burr Feb 02 '16 at 23:47
  • @MichaelBurr Because I want to avoid the overhead of the copying of the iterator. And out of curiosity "can it be done". – quant_dev Feb 02 '16 at 23:51

3 Answers3

3

You may use a proxy for the post increment:

#include <iostream>

class input_iterator
{
    private:
    class post_increment_proxy
    {
        public:
        post_increment_proxy(int value) : value(value) {}
        int operator * () const { return value; }

        private:
        int value;
    };

    public:
    post_increment_proxy operator ++ (int) {
        post_increment_proxy result{value};
        ++value;
        return result;
    }

    private:
    int value = 0;
};


int main() {
    input_iterator i;
    std::cout << *i++ << '\n';
    std::cout << *i++ << '\n';
    std::cout << *i++ << '\n';
}
  • You certainly can't use this to implement `std::istream_iterator` (since it specifies that the return type from `operator++(int)` is an `istream_iterator`, not some proxy type. – Jerry Coffin Feb 03 '16 at 00:09
  • @JerryCoffin The requirement is `(void)i++` –  Feb 03 '16 at 00:12
  • Ummm...what? The requirement, in n4296, [istrream.iterator] is: `istream_iterator operator++(int);` – Jerry Coffin Feb 03 '16 at 00:14
  • @JerryCoffin At least since "N3337" the requirement for an input_iterator (!) is `(void)r++` –  Feb 03 '16 at 16:57
1

First of all, the iterator is copyable, even though in the case of an InputIterator, the copy acts more like a move (specifically, after you increment any one copy of the iterator, you shouldn't dereference any other copy of it).

Nonetheless, there shouldn't be any problem with copying the iterator--in fact, most of the library (and a lot of other code) assumes that iterators are "lightweight" objects; copying them is cheap, so (for one obvious example) they're typically passed by value, not by reference.

So, a somewhat simplified stream iterator might look something like this:

template <class T>
class istream_iterator {
    std::istream *is;
    T data;
public:
    istream_iterator(std::istream &is) : is(&is) { ++(*this); }
    istream_iterator() : is(nullptr) { }

    istream_iterator &operator++() { (*is) >> data; return *this; }

    // So here's the post-increment: it just saves a copy of itself, then
    // reads the next item (which increments the iterator), and finally
    // returns the copied object, which will return the previously-read item
    // from the stream when/if dereferenced.
    istream_iterator operator++(int) { 
        // Note: this uses the compiler-generated copy constructor. Assuming
        // a `T` is copy-constructible, this works fine--other than a T,
        // we're only copying a pointer.
        istream_iterator temp = *this; 
        (*is) >> data; 
        return temp; 
    }

    T const &operator*() const { return data; }

    bool operator !=(istream_iterator &end) { return (*is).good(); }
    bool operator ==(istream_iterator &end) { return !(*is).good(); }
};

This "cheats" on a couple of fairly minor points--for example, two default-constructed iterators should compare equal to each other, which this doesn't bother to implement (and which virtually nobody ever uses or cares about). In normal use, you create one iterator from a stream, and default construct another. A comparison between the two should return true if and only if the first has reached the end of the input stream (or reading has failed for some reason, anyway). Likewise, this leaves out the implementation operator->, and a few of the typedefs required of a standard iterator type (value_type, traits_type, istream_type, etc.) None of these is relevant to the question at hand though (and all are a matter of adding the required code, not making any substantial changes to the code that's already here).

A quick demo of the code could look something like this:

int main() {
    istream_iterator<char> i(std::cin), end;

    while (i != end)
        std::cout << *i++;
}

This will copy characters from standard input to standard output, skipping white space, because operator>> skips whitespace by default (but you can eliminate that with noskipws if you want).

Jerry Coffin
  • 476,176
  • 80
  • 629
  • 1,111
0

I once ran into this, too. IMO, the requirement makes no sense. It’s a burden on the implementer of an input iterator and the behavior can be implemented on the side of the user of the iterator quite trivially.

My take is this:

// -- inputitproxy.hpp ------

template <typename It, bool backward>
class increment_decrement_proxy
{
    It& it;
    bool incremented = false;
    void push()
    {
        if (!incremented)
        {
            if constexpr (backward) --it; else ++it;
        }
    }

public:
    increment_decrement_proxy() = delete;
    increment_decrement_proxy(const increment_decrement_proxy&) = delete;
    increment_decrement_proxy(increment_decrement_proxy&&) = delete;

    increment_decrement_proxy(It& it) : it{it} {}
    auto operator*() -> decltype(*it)
    {
        decltype(auto) result = *it;
        push();
        incremented = true;
        return result;
    }
    ~increment_decrement_proxy() { push(); }
    // ~increment_decrement_proxy() && { push(); }
    // ~increment_decrement_proxy() & = delete;
};

template <typename It>
using increment_proxy = increment_decrement_proxy<It, 0>;
template <typename It>
using decrement_proxy = increment_decrement_proxy<It, 1>;

You use it like this:

// -- inputit.hpp -----------

template <typename T>
class input_it
{
    T* ptr;

public:
    // boilerplate
    input_it(T* ptr) : ptr{ptr} {}
    T& operator*() const { return *ptr; }
    T* operator->() const { return ptr; }
    input_it& operator++() { ++ptr; return *this; }
    input_it& operator--() { --ptr; return *this; }

    // actual usage:
    auto operator++(int) { return increment_proxy{*this}; }
    auto operator--(int) { return decrement_proxy{*this}; }
};

A working example:

#include "inputit.hpp"

#include <iostream>

struct no_copy
{
    int x;
    explicit no_copy(int i) : x(i) {}
    no_copy(const no_copy&) = delete;
};

int main()
{
    no_copy stuff[] { no_copy{1}, no_copy{2}, no_copy{3} };
    input_it it { &stuff[0] };
    std::cout << it->x << std::endl; // prints 1
    (void)it++;

    // Note: an input iterator is not supposed to be used like that.
    //auto it2 = it++; // compiles, but does not actually increment

    std::cout << it->x << std::endl; // prints 2
    std::cout << (*it++).x << std::endl; // prints 2 again
    std::cout << it->x << std::endl; // prints 3
}

The two commented-out destructors don’t work because reference annotations are not allowed on destructors. If that worked, the line initializing it2 would not compile.

You can try it out here.

Quirin F. Schroll
  • 1,302
  • 1
  • 11
  • 25