Opened 9 years ago

Closed 8 years ago

#9812 closed Bugs (fixed)

BinaryOperation boost::transform only stops when it reaches the end of rng1, even if rng2 is shorter

Reported by: David Sanders <david.sanders@…> Owned by: Neil Groves
Milestone: Boost 1.56.0 Component: range
Version: Boost 1.54.0 Severity: Problem
Keywords: Cc:

Description

Here's a complete program that reproduces the problem:

#include <boost/range/algorithm/transform.hpp> #include <boost/range/irange.hpp>

namespace std {

ostream& operator<<(ostream& stream, const pair<int, int>& p) {

return stream << "(" << p.first << ", " << p.second << ")";

}

}

int main() {

boost::transform(

boost::irange(0, 100), boost::irange(0, 10), std::ostream_iterator<std::pair<int,int>>(std::cout, "\n"), [](const int left, const int right) { return std::make_pair(left, right); });

return EXIT_SUCCESS;

}

I compile on Fedora 20 using g++ 4.8.2. Here's the output I get:

g++ transform.cpp -std=c++11 && ./a.out (0, 0) (1, 1) (2, 2) (3, 3) (4, 4) (5, 5) (6, 6) (7, 7) (8, 8) (9, 9) a.out: /usr/include/boost/range/algorithm/transform.hpp:63: OutputIterator boost::range_detail::transform_impl(SinglePassTraversalReadableIterator1, SinglePassTraversalReadableIterator1, SinglePassTraversalReadableIterator2, SinglePassTraversalReadableIterator2, OutputIterator, BinaryFunction) [with SinglePassTraversalReadableIterator1 = boost::range_detail::integer_iterator<int>; SinglePassTraversalReadableIterator2 = boost::range_detail::integer_iterator<int>; OutputIterator = std::ostream_iterator<std::pair<int, int> >; BinaryFunction = main()::lambda0]: Assertion `first2 != last2' failed. Abort

Here's the code I believe to be broken in boost/range/algorithm/transform.hpp:

template< class SinglePassTraversalReadableIterator1,

class SinglePassTraversalReadableIterator2, class OutputIterator, class BinaryFunction >

inline OutputIterator transform_impl(SinglePassTraversalReadableIterator1 first1,

SinglePassTraversalReadableIterator1 last1, SinglePassTraversalReadableIterator2 first2, SinglePassTraversalReadableIterator2 last2, OutputIterator out, BinaryFunction fn)

{

for (; first1 != last1; ++first1, ++first2) {

BOOST_ASSERT( first2 != last2 ); *out = fn(*first1, *first2); ++out;

} return out;

}

Here's my proposed fix:

template< class SinglePassTraversalReadableIterator1,

class SinglePassTraversalReadableIterator2, class OutputIterator, class BinaryFunction >

inline OutputIterator transform_impl(SinglePassTraversalReadableIterator1 first1,

SinglePassTraversalReadableIterator1 last1, SinglePassTraversalReadableIterator2 first2, SinglePassTraversalReadableIterator2 last2, OutputIterator out, BinaryFunction fn)

{

for (; first1 != last1 && first2 != last2; ++first1, ++first2) {

*out = fn(*first1, *first2); ++out;

} return out;

}

I added a check that first2 != last2 and removed the ASSERT.

Here's the relevant documentation: BinaryOperation version:

transform assigns the value z to each element [out, out + min(distance(rng1), distance(rng2))), z = fun(x,y) where x is the corresponding value in rng1 and y is the corresponding value in rng2. This version of transform stops upon reaching either the end of rng1, or the end of rng2. Hence there isn't a requirement for distance(rng1) == distance(rng2) since there is a safe guaranteed behaviour, unlike with the iterator counterpart in the standard library.


Please note that the original implementation doesn't meet the specification described above, whereas the new implementation does. BinaryOperation boost::for_each has a similar specification, but it implements it correctly.

Boost Rocks! Keep up the stellar work!

Don't hesitate to contact me if any of the above needs further explanation.

Thanks, David

Change History (3)

comment:1 by Neil Groves, 8 years ago

Milestone: To Be DeterminedBoost 1.57.0
Status: newassigned

This is fixed on the "develop" branch.

comment:2 by anonymous, 8 years ago

Milestone: Boost 1.57.0Boost 1.56.0

Release window for 1.56 has been extended so I can put this into 1.56.

comment:3 by Neil Groves, 8 years ago

Resolution: fixed
Status: assignedclosed

Merged to master and ready for the next release.

Note: See TracTickets for help on using tickets.