Search code examples
c++c++11boostboost-thread

Can anybody explain this unexpected result from boost::thread?


Consider the following program built against boost v1.53. I would expect z = 10 as output, but the program prints z = -1294967296. Can anybody explain why?

// g++ -std=c++11 -O2 -Wall -pedantic -lboost_system -lboost_thread main.cpp && ./a.out
#include <iostream>
#include <iomanip>
#include <boost/thread.hpp> // boost v1.53

struct S {
    boost::packaged_task<void> task;
    boost::thread th;
    void async_start(int z) {
        auto worker = [=]() {
            boost::this_thread::sleep_for(boost::chrono::seconds{1});
            std::cout << "z = " << z << '\n' << std::flush; // does not z have the same value as the async_start arg?
        };
        task = boost::packaged_task<void>{worker}; // is not 'worker' copied?
        th = boost::thread{std::move(task)}; // start
    }
};

int main() {
    S s;
    s.async_start(10);
    boost::this_thread::sleep_for(boost::chrono::seconds{3}); // wait some time to make the thread finish
    s.th.join();
}

// z = -1294967296

Solution

  • This appears to be Boost bug 8596, fixed in Boost 1.54.

    Briefly, in C++11 mode, boost::packaged_task's constructor is broken when passed an lvalue, storing a reference (!) instead of a copy. The functor is taken by forwarding reference, meaning that the template parameter was deduced to be an lvalue reference when an lvalue is passed. The code apparently neglected to strip the referenceness.

    As confirmation, passing a prvalue to packaged_task's constructor (by using the lambda expression directly) fixes the problem:

        task = boost::packaged_task<void>{[=]() {
            boost::this_thread::sleep_for(boost::chrono::seconds{1});
            std::cout << "z = " << z << '\n' << std::flush;
        }}; 
    

    and so does passing an xvalue by using std::move(worker).