Getting unexpected result when compiling with clan

2019-04-08 21:34发布

问题:

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.

回答1:

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.



回答2:

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.