Boost.Foreach and Compiler Bugs

Posted on 06 August 2013  •  Permalink  •  Category c++

Boost is a library that definitively boosts your productivity writing C++ code. For instance, for loops can be made a lot more readable if you use Boost.Foreach. It’s almost as if you are writing Python, Objective-C or even Java, which support a foreach construct natively.

However, like always, hiding things behind an abstraction can be a problem. Especially when the abstraction depends on a compiler bug. That’s the case with BOOST_FOREACH in Boost versions prior to Boost 1.47, which relies on a compiler bug for rvalue detection. And it so happens that is compiler bug is fixed in GCC 4.6, which breaks older Boost versions.

Ubuntu 12.04 actually ships with Boost 1.46.1 and GCC 4.6.3. A deadly combination as it seems. Just try to run this little C++ program:

#include <boost/shared_ptr.hpp>
#include <boost/foreach.hpp>
#include <vector>
#include <iostream>

const std::vector<boost::shared_ptr<int> > f()
{
  return std::vector<boost::shared_ptr<int> >(4,
    boost::shared_ptr<int>(new int(12)));
}

int main(int argc, char **argv)
{
  BOOST_FOREACH(const boost::shared_ptr<int> &pi, f())
  {
    std::cout << *pi << std::endl;
  }
}

You would expect to see 4 lines printed with the number 12. Instead you will get this:

$ g++ test.cpp -o test
$ ./test
test: /usr/include/boost/smart_ptr/shared_ptr.hpp:412:
  boost::shared_ptr<T>::reference boost::shared_ptr<T>::operator*() const
  [with T = int, boost::shared_ptr<T>::reference = int&]:
    Assertion `px != 0' failed.
Aborted (core dumped)

So what’s actually happening here? Internally Boost.Foreach will try to detect whether the container expression is an lvalue or an rvalue. In the example above the result from f() is such rvalue: a temporary object that will be gone as soon as it goes out of scope. Boost.Foreach takes precautions for that by copying the rvalue internally such that it will live as long as the scope of the for loop.

Now what happens if rvalue detection fails in above example? It means that the vector returned by f() will be gone before the statements in the loop get executed. The destruction of the vector means the smart pointers are destructed too. As a result, trying to access them in the std::cout statement will trigger the px != 0 assertion.

Fortunately this problem is fixed in Boost 1.47 and up. For more information on how Boost.Foreach works, see Conditional Love: FOREACH Redux.