Boost logo

Boost :

Subject: Re: [boost] Constant iterator of Single Pass Range
From: Jan Hudec (bulb_at_[hidden])
Date: 2014-05-05 12:08:02


Dne 05.05.2014 13:40, Neil Groves napsal:
> On Mon, May 5, 2014 at 11:03 AM, Jan Hudec <bulb_at_[hidden]> wrote:
>
>> Hello All,
>>
>> The documentation of [Single Pass Range][1] in Boost.Range says that
>> there
>> should be both `iterator` and `const_iterator` associated types and
>> both
>> const and non-const overloads for `begin` and `end`. However if it
>> is
>> really a single-pass range, I don't see a way to implement a
>> `const_iterator` that would work on `const` reference to the range,
>> because
>> the `operator++` of the iterator involves calling a non-const method
>> of the
>> range.
>>
>
> I think that your statements indicate that you have a
> misunderstanding
> about implementing iterators. The word "const" does not refer to the
> iterator type itself but to the reference type. It is not possible
> without
> resort to very perverse use of the "mutable" keyword to implement a
> const_iterator to refer to any type. Of course, const_iterator is
> part of
> every standard container so this is not an idiom that is specific to
> Boost.Range.

All containers are at least forward ranges, not merely single pass
ones.
Since iterating a forward range does not change it, there is no problem
for it
to have a `const` `begin()` and `end()` methods (returning const
iterators).

What I had issue with is single pass range. Consider something that
behaves
like `std::istream`. The `std::istream_iterator` can be only
constructed from
non-const reference to `std::istream`, because the
`std::istream_iterator::operator++` (and the constructor itself) call
the
`std::istream::operator>>`, which is non-const.

Of course for `std::istream` a wrapper is needed anyway, because the
stream can
return various types, so the wrapper has to specify which one is
desired. But
if I have a class that behaves similarly, but returns a specific type,
I still
can't make it a range, because I still can't give it a `begin` that
would accept
const reference (I _can_ usually give it `end`, because for this kind
of objects
the end iterator is dummy, but that won't help).

>> For example the `std::istream_iterator::operator++` calls
>> `std::istream::operator>>`, which is non-const and so there is no
>> `const_istream_iterator`. The `istream_range` cheats around this a
>> little,
>> because the range
>> is a wrapper around the stream, not the stream itself. But when I
>> have
>> similarly behaving class that I can make itself be a range, do I
>> really
>> have to work around this using a wrapper, or can the requirement be
>> relaxed
>> in practice?
>
> I believe that you don't have a problem! A const_iterator can be
> implemented with non-const member functions. There are examples
> within the
> Boost.Range unit test code that show the use of extending Boost.Range
> for
> user defined types. These show the various methods for implementing
> this.
> There is also documentation that has an example extending via the
> const and
> mutable meta-functions here:
>
> http://www.boost.org/doc/libs/1_55_0/libs/range/doc/html/range/reference/extending/method_2.html

Well, it can't be implemented using non-const methods of the _range_
_itself_.

If I have a stream-like object

     class int_reader {
        ...
        int get_next(); // NOT const
     };

than I can create iterators for it. But the iterators will look like

     class int_reader_iterator :
         boost::iterator_facade<int_reader_iterator, int,
boost::single_pass_traversal_tag, int const &>
     {
         int_reader *reader; // NOT const
         ...
         void advance() { value = reader->get_next(); } // must NOT be
const HERE
         ...
         int_reader_iterator(int_reader &reader) : reader(&reader) { }
// can't be const here
     };

And therefore I can't provide

     int_reader_iterator range_begin(int_reader_iterator const &);

And therefore I can't make `int_reader` a range. I can make a range
adapter for it,
that will break the const chain like `boost::istream_range` does.

Now why I wanted that was that I wanted to work with temporaries and
was concerned
that I would get dangling reference, because BOOST_FOREACH will extend
life of the
passed range by binding it to a reference, but that does not extend to
arguments of
the expression. Basically I thought of writing something like

     BOOST_FOREACH(int i,
boost::input_range<int>(std::ifstream("file.txt")))
        ...

Now this won't extend the life of the ifstream, but fortunately it
won't compile
either, because input_range requires a non-const reference.

And if I had my object that would directly be range, but didn't have
const begin,
well, then BOOST_FOREACH wouldn't accept a temporary either, because
temporary
only binds to const reference. So the object that needs to be modified
can't
be a temporary whatever I do and I can wrap it and don't need to
bother.

What is still a problem is wrapper of a wrapper, but it's enough to
simplify that
to one level of wrapper.

--
Jan Hudec <bulb_at_[hidden]>

Boost list run by bdawes at acm.org, gregod at cs.rpi.edu, cpdaniel at pacbell.net, john at johnmaddock.co.uk