Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

std::thread with movable, non-copyable argument

The following program doesn't build in VS11 beta, gcc 4.5, or clang 3.1

#include <thread>
#include <memory>

int main() {
    std::unique_ptr<int> p;
    std::thread th([](std::unique_ptr<int>) {

    },std::move(p));
    th.join();
}

This is because the argument type is not copyable, but the implementation attempts to copy it.

As far as I can tell, this program is well formed and should work. The requirements for std::thread seem to imply that movable, non-copyable arguments should work here. Specifically it says that the callable object and each argument shall satisfy the MoveConstructible requirements, and that INVOKE(DECAY_COPY(std::forward<F>(f)),DECAY_COPY(std::forward<Args>(args))...) shall be a valid expression.

In this case I think expression works out to something like:

template <class T> typename std::decay<T>::type decay_copy(T&& v)
{ return std::forward<T>(v); }

std::unique_ptr<int> p;
auto f = [](std::unique_ptr<int>) {};

decay_copy(f)(decay_copy(std::move(p)));

And I don't think this is supposed to involve a copy of p. gcc at least can compile this expression, though VS11 does not.

  1. Am I wrong about the requirements and the arguments must be copyable?
  2. Does the standard leave any leeway on this issue for implementations to copy arguments?
  3. Or are the implementation I tried non-conforming?
like image 633
bames53 Avatar asked Apr 03 '12 21:04

bames53


2 Answers

From 30.3.1.2, paragraph 3 and 4 of N3337:

template <class F, class ...Args> explicit thread(F&& f, Args&&... args);

Requires: F and each Ti in Args shall satisfy the MoveConstructible requirements. INVOKE (DECAY_-COPY ( std::forward<F>(f)), DECAY_COPY (std::forward<Args>(args))...) (20.8.2) shall be a valid expression.

Effects: Constructs an object of type thread. The new thread of execution executes INVOKE (DECAY_-COPY ( std::forward<F>(f)), DECAY_COPY (std::forward<Args>(args))...) with the calls to DECAY_COPY being evaluated in the constructing thread. Any return value from this invocation is ignored. [ Note: This implies that any exceptions not thrown from the invocation of the copy of f will be thrown in the constructing thread, not the new thread. —end note ] If the invocation of INVOKE (DECAY_COPY ( std::forward<F>(f)), DECAY_COPY (std::forward<Args>(args))...) terminates with an uncaught exception, std::terminate shall be called.

So yes, this should work. If it doesn't, then that's a bug in your implementation.

Do note that any parameter movement/copying will happen on the new thread. You're passing references to another thread, so you need to make sure that they still exist until that thread starts.

like image 67
Nicol Bolas Avatar answered Oct 07 '22 15:10

Nicol Bolas


As an alternative, and as the standard std::thread idiom, you can pass a reference wrapper:

int p;
std::thread([](int & x) { /* ... */ }, std::ref(p));

This creates an object of type std::reference_wrapper<int>, which has value semantics and wraps a reference to an int (i.e. copying the wrapper aliases the reference).

like image 45
Kerrek SB Avatar answered Oct 07 '22 15:10

Kerrek SB