std::thread with movable, non-copyable argument

Go To StackoverFlow.com

9

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?
2012-04-03 21:34
by bames53
You seem to be passing the thread argument by copy (as per the anonymous function signature). Shouldn't the argument type be std::unique_ptr<int>&& or const std::unique_ptr<int>& - André Caron 2012-04-03 21:36
@André : There is no such thing as passing by copy; passing the argument by value will copy or move depending on whether the caller passes an lvalue or an rvalue - ildjarn 2012-04-03 21:47
@ildjarn: sorry, I meant "by value", not "by copy". It slipped my mind that passing arguments by value will select the move constructor if one is available - André Caron 2012-04-03 22:23


14

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.

2012-04-03 21:47
by Nicol Bolas
And it works with g++, as of version 4. - je4d 2012-04-03 22:25
And now I can no longer reproduce the error in clang either, even though the code I used earlier is in a source repository and I have the exact command line in my history. I guess I should recheck vs11 too - bames53 2012-04-04 00:55
Actually it looks like the issue was an old version of libc++ vs the latest - bames53 2012-04-04 00:56
This code also works with my Just::Thread library with g++ 4.5 & 4.6 and MSVC 2010 - Anthony Williams 2012-04-04 07:29


3

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).

2012-04-03 22:19
by Kerrek SB