Boost logo

Boost :

Subject: Re: [boost] Boost range - Add variadic join/zip
From: Jonathan Wakely (jwakely.boost_at_[hidden])
Date: 2013-05-10 08:44:07


On 10 May 2013 12:55, Neil Groves wrote:
> On Fri, May 10, 2013 at 11:28 AM, Jonathan Wakely
>>
>> It looks as though it's undefined behaviour to zip ranges of different
>> lengths, because you'll walk off the end of the shorter ranges.
>>
> My variadic zip stops at the end of the shortest range, which seems to
>> be consistent with zip functions in most other languages I've looked
>> at.
>>
>>
> I like being able to avoid the cost of checking for the end of every item
> in the zip especially for non-random access iterators. In anything I put
> into Boost.Range I think it of paramount importance to obey the zero
> overhead principle. It seems that it would be simple to allow both end
> detection mechanisms.

Hi Neil,

That makes sense. My implementation always truncates the ranges to
the shortest length but I should make it unchecked and then provide a
second interface to do the checking and truncating if needed.

>> Your adaptors also get dangling references if used with rvalue ranges,
>> although this is a problem with the existing boost range adaptors too.
>>
>
> Yes, this has come up numerous times. It's a problem far beyond just ranges
> and range adaptors. Knowing you a little, I suspect you have a solution I
> have not thought of to better deal with the issue.
>
> Is the variadic zip iterator you implemented public?

I don't have my own zip_iterator (well, I do, but I'm still working on
it :-) but my zip() function is at
https://gitorious.org/redistd/redistd/blobs/master/include/redi/zip.h
and just uses boost::zip_iterator.

The solution to the dangling reference problem is surprisingly simple in C++11:

template<typename Traversable>
struct adaptor
{
  explicit adaptor(Traversable&& r) : range(r) { }

  Traversable range;

  auto begin() const -> decltype(range.begin()) { return range.begin(); }

  auto end() const -> decltype(range.end()) { return range.end(); }
};

// When called with lvalue, Traversable deduces to R&
// When called with rvalue, Traversable deduces to R
template<typename Traversable>
adaptor<Traversable>
adapt(Traversable&& t)
{
  return adaptor<Traversable>(std::forward<Traversable>(t));
}

When you call adapt(lvalue) you get an adaptor<R&> and the member
adaptor<R&>::range is a reference to the lvalue. This makes the
lvalue case cheap as there's no copying. (This is what your adaptors
do today.)

When you call adapt(rvalue) you get an adaptor<R>and the member
adaptor<R>::range is a copy of the rvalue, initialized by a move. The
move construction is not as cheap as binding a reference, but it's
safe and avoids a dangling reference.

This would be very hard to do without rvalue references, and doesn't
play nicely with make_iterator_range, because you don't want to make
an iterator_range that refers to a temporary range, or you're back to
the dangling reference problem. I've used this solution in isolated
cases, but haven't got a generic solution to the problem.


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