I found a bug in my code that only happens when I enable compiler optimizations -O1 or greater. I traced the bug and it seems that I can't use the boost type_erased adaptor on a boost transformed range when optimizations are enabled. I wrote this c++ program to reproduce it:
#include <iostream>
#include <vector>
#include <boost/range/adaptor/transformed.hpp>
#include <boost/range/adaptor/type_erased.hpp>
using namespace boost::adaptors;
using namespace std;
int addOne(int b) {
return b + 1;
}
int main(int, char**) {
vector<int> nums{ 1, 2, 3 };
auto result1 = nums | transformed(addOne) | type_erased<int, boost::forward_traversal_tag>();
auto result2 = nums | transformed(addOne);
auto result3 = nums | type_erased<int, boost::forward_traversal_tag>();
for (auto n : result1)
cout << n << " ";
cout << endl;
for (auto n : result2)
cout << n << " ";
cout << endl;
for (auto n : result3)
cout << n << " ";
cout << endl;
}
When I run this program without any optimizations, I get the following output:
2 3 4
2 3 4
1 2 3
When I run it with the -O1 flag, I get the following:
1 1 1
2 3 4
1 2 3
I am using clang++ to compile it. The version of clang that I am using is:
Apple LLVM version 8.0.0 (clang-800.0.38)
I don't know if I am doing something wrong, or if it is a boost/clang bug.
edit:
Changed it to
type_erased<int, boost::forward_traversal_tag, const int>()
and it works now. The third template argument is the reference type, setting the reference to const prolongs the timespan of the temporary created by the transformed.
EDIT In fact there's more to this than meets the eye. There is another usability issue, which does address the problem. See OP's self-answer
You're falling into the number 1 pitfall with Boost Range v2 (and Boost Proto etc.).
nums | transformed(addOne)
is a temporary. The type_erased
adaptor stores a reference to that.
After assigning the type-erased adaptor to the resultN
variable, the temporary is destructed.
What you have is a dangling reference :(
This is a highly unintuitive effect, and the number 1 reason why I limit the use of Range V2 in my codebase: I've been there all too often.
Here is a workaround:
auto tmp = nums | transformed(addOne);
auto result = tmp | type_erased<int, boost::forward_traversal_tag>();
-fsanitize=address,undefined
confirms that the UB is gone when using the named temporary.
Using
type_erased<int, boost::forward_traversal_tag, const int>()
works. The third template argument is the reference type, setting the reference to const prolongs the timespan of the temporary created by the transformed.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With