Boost logo

Boost :

From: David Abrahams (dave_at_[hidden])
Date: 2006-02-02 16:52:41


Thorsten Ottosen <tottosen_at_[hidden]> writes:

> David Abrahams wrote:
>> Thorsten Ottosen <tottosen_at_[hidden]> writes:
>
>>>1. leave the return type as it is.
>>>
>>>2. return an iterator_range describing the range [found,end)
>>>this allows us to do stuff like
>>>
>>> namespace br = boost::ranges;
>>> br::erase( rng, br::unique(rng) );
>>>
>>>3. inspired by John Torjo's range lib, provide a whole list of possible
>>> return values:
>
> [snip]
>
>> 2 is the answer for me.
>>
>>
>>>the default would be the range [found,end), but the you can pick
>>>different slices or simply iterators. In the above scheme, "found" is
>>>the iterator normally returnes by the algorithm, "next" means
>>>boost::next(found) and "prior" means boost::prior(found).
>>
>>
>> And that works for ranges without bidirectional iterators, e.g. slist?
>
> "prior" can of course not work with bidirectional iterators.

In that case, this should *not* be integrated with the algorithms, but
should instead be provided (if it's needed) as a separate layer that
can adjust the endpoints of a range:

    drop_front(r) -> new view of r with the begin iterator incremented
    grow_front(r) -> new view of r with the begin iterator decremented
    drop_back(r) -> new view of r with the end iterator decremented
    grow_back(r) -> new view of r with the end iterator incremented

Okay, I see from your points below that the "grow" variants can be
dangerous.

>>>The advantage of the latter approach is
>>>
>>>- flexibility/power
>>>- safety (we can tjeck that next(found) and prior(found) stays in range)
>>>
>>>The disadvantage is that it will require twice as many overloads in
>>>C++03 (since we can't put defaults template arguments in function
>>>templates).
>>
>>
>> And it's complex.
>
> implementation wise or interface wise?

Yes. And documentation-wise. And comprehension-wise.

>> I'm wary of introducing any such complexity without a strong catalog
>> of use cases.
>
> basically it makes splitting of ranges way easier and safer.
>
> assume I want to find the first occurence of 5 and then copy the
> range [begin,found] (that is, [begin,next(found)) ) somewhere:
>
> std::vector<int> v = ...;
> std::vector<int>::iterator i = find( v, 5 );
> if( i != v.end() )
> {
> ++i;
> copy( v.begin(), i, out );
> }
>
> vs
>
> copy( find<return_begin_next>( v, 5 ), out ); (*)

    copy( drop_front( find( v, 5 ) ), out );

it's even shorter.

> If found == v.end(), the returned ranges are empty and so you completely
> avoid forgetting about checking for the end iterator.
>
> Having only (2) we can still, however, do a little better than above:
>
> copy( find( c, 5 ).advance_end(1), out )
>
> but now we can't be sure we don't iterator past the end.

Okay, that's a different case. Why would you ever want to advance the
*end* of a find? It's really beside the point, though.

Feh.

If this is an important case (still not convinced), I would consider
generalizing find with this overload:

      find( -1, v, 5 )

That is, "find the element before the one that's equal to 5". And I
would *certainly* make it work for forward ranges with somewhat
reduced efficiency. That's consistent with the spirit of generic
programming. I don't think it's necessary to have the policy be a
compile-time thing. But if it were, I'd still pass it by by value.

Anyway, how do you check for success? What if 5 is the first element?
Do you get an empty range or do you just get v back?

> Thus we would
> be forced to create a temporary.

> -Thorsten
>
> (*) without much extra work, I could probably get a generalized
> version like
>
> xx_next<N>
> xx_prior<N>
>
> to work.

No offense, but bleah. Keep it simple.

-- 
Dave Abrahams
Boost Consulting
www.boost-consulting.com

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