Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Getting unexpected result when compiling with clang optimization

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.

like image 435
user2253609 Avatar asked Nov 08 '16 05:11

user2253609


Video Answer


2 Answers

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.

like image 141
sehe Avatar answered Oct 04 '22 13:10

sehe


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.

like image 25
user2253609 Avatar answered Oct 04 '22 13:10

user2253609