In the test case below, I use boost::combine to iterate on output of a function getPoints()
.
Expected Output
I expect (1, 2, 3) printed 6 times; since I effectively zip two lists -
([point, point, point], [point, point, point]).
Actual Output
The output is surprising to me, and wrong. The first two lines are off suggesting memory corruption?
(0, 0, 3) // <-- wrong!
(52246144, 0, 3) // <-- wrong! memory corruption?
(1, 2, 3)
(1, 2, 3)
(1, 2, 3)
(1, 2, 3)
This can also be verified online here, http://cpp.sh/622h4 .
Is this a bug?
Code below -
#include <iostream>
#include <vector>
#include <boost/range/combine.hpp>
struct Point {
int x, y, z;
};
const std::vector<Point> getPoints() {
// There is only one Point in the entire code, which is (1, 2, 3).
const Point point = {1, 2, 3};
// Return a vectore of 3 copies of the point (1, 2, 3).
return {point, point, point};
}
int main() {
// Zip over two copies of 3-tuples of {1, 2, 3}.
for (const auto& zipped : boost::combine(getPoints(), getPoints())) {
auto p1 = zipped.get<0>();
auto p2 = zipped.get<1>();
// Expected output is (1, 2, 3), six times.
std::cout << "(" << p1.x << ", " << p1.y << ", " << p1.z << ")" << std::endl;
std::cout << "(" << p2.x << ", " << p2.y << ", " << p2.z << ")" << std::endl;
}
return 0;
}
You have undefined behavior here as you access a dangling reference. This can be fixed by
const auto points1 = getPoints();
const auto points2 = getPoints();
for (const auto& zipped : boost::combine(points1, points2)) {
// ...
}
Rvalue references are always problematic when dealing with range libraries. Obviously, a range algorithm like boost::combine
doesn't copy the argument. And it creates a new proxy range object, which makes it impossible to extend the lifetime of the temporary range passed in.
Contrary, a range-based for loop for(const auto& item: getPoints()) {...}
expands to
{
auto && __range = getPoints();
for (auto __begin = begin_expr, __end = end_expr; __begin != __end; ++__begin) {
range_declaration = *__begin;
loop_statement
}
}
where the lifetime of getPoints()
is extended by binding it to an rvalue reference. Imagine a function template combine
as
template<class Rng>
auto combine(Rng&& rng) {
auto && == range; // Nice try but doesn't help
// ...
return someProxyRange;
}
This function template can't do anything about extending the lifetime of rng
, as it acts in a different scope than rng
, which comes from the client side. In a range based for loop, this is different. The scope of the temporary (eg getPoints()
) and the forwarding reference auto&& __range
are at the same scope, hence the lifetime can be extended.
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.