Passing move-only function arguments to boost::thread constructor

556 Views Asked by At

The following works for std::thread. It prints 10 as output, which is what I desire.

void foo(std::unique_ptr<int> && in) {
  std::cout << *in;
}

int main(){
  auto in = std::make_unique<int>(10);
  std::thread t(foo, std::move(in));
  t.join();
} 

But, similar attempt with Boost 1.72 fails to compile

void foo(std::unique_ptr<int> && in) {
    std::cout << *in;
}

int main(){

    auto in = std::make_unique<int>(10);
    boost::thread t(foo, std::move(in));
    t.join();
} 
Error : note: copy constructor of 'list1<boost::_bi::value<std::unique_ptr<int>>>' is implicitly deleted because base class 'storage1<boost::_bi::value<std::unique_ptr<int>>>' has a deleted copy constructor
template< class A1 > class list1: private storage1< A1 >

I find this surprising, because the documentation for boost::thread states the following :

Thread Constructor with arguments template <class F,class A1,class A2,...> thread(F f,A1 a1,A2 a2,...);

Preconditions: F and each An must be copyable or movable.

Since I am passing a std::unique_ptr as argument, I am meeting the 'movable' criterion. So, I wonder why is boost thread constructing the std::unique_ptr? Shouldn't it move the std::unique_ptr into the thread object, and then move it further into the thread function like the implementation for std::thread does?

3

There are 3 best solutions below

3
n. m. could be an AI On

Per documentation, boost::thread uses the arguments in exactly the same way boost::bind does, and this combination of function and argument is unsuitable for boost::bind (and for std::bind for that matter).

std::bind(foo, std::move(in))(); // error
boost::bind(foo, std::move(in))(); // error

std::thread is more robust than either std::bind or boost::bind.

If you need to move the argument to foo, you need to wrap it in a function or a lambda that accepts it by non-const lvalue reference and moves it to foo. Otherwise, just change foo to accept a non-const lvalue reference argument.

0
rustyx On

Looks like a bug in Boost.Thread documentation. By default it doesn't support move-only arguments since it passes them through to boost::bind by-value instead of forwarding.

But there's a flag BOOST_THREAD_PROVIDES_VARIADIC_THREAD that enables boost::thread rvalue-constructors. It is set automatically when another poorly documented variable, BOOST_THREAD_VERSION > 4.

#define BOOST_THREAD_VERSION 5
#include <boost/thread.hpp>
. . .

Live demo

Another possible workaround is to use a lambda.

void foo(std::unique_ptr<int> in) {
    std::cout << *in;
}

int main() {
    auto in = std::make_unique<int>(10);
    boost::thread t([in = std::move(in)]() mutable { foo(std::move(in)); });
    t.join();
}

Live demo 2

0
Yakk - Adam Nevraumont On

The boost thread library uses boost bind, and boost bind assumes you can call the callable repeatedly, so doesn't move out of it. You can fake it.

template<class T>
struct move_it_t {
  T t;
  operator T(){ return std::move(t); }
};
template<class T>
move_it_t<typename std::decay<T>::type> move_it(T&& t)
  return std::move(t);
};

then

auto in = std::make_unique<int>(10);
boost::thread t(foo, move_it(in));
t.join();

what this does is wrap in in a type that, when cast to T, moves instead of copies.