thread(F&&) fails when F is not MoveAssignable

[Sorry for the cross-post, but cfe-users doesn't seem to be active yet.]

The snippet at the end of this message fails to compile with clang-libc++.

Is LaunchByMove() invalid C++11, or is this a libc++ bug?

I have a body of code written using the failing LaunchByMove pattern. I guess
I'll have to work around the issue, perhaps with the LaunchByCopy pattern.
Enforcing that style may prove error-prone, though.

The relevant section in N3242 is this:

  30.3.1.2 thread constructors
  …

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

  Given a function as follows:

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

  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.

There doesn't appear to be any Swappable or MoveAssignable requirement for
template argument F, at least in the draft.

— Gordon

cat <<'EOT' >test.cpp
#include <thread>
class CopyableNotAssignable {
  int &n_;
public:
  CopyableNotAssignable(int &n) : n_(n) { }
};
void LaunchByMove() {
  int n;
  std::thread(CopyableNotAssignable(n)).join(); // expect-error
}
void LaunchByCopy() {
  int n;
  CopyableNotAssignable cna(n);
  std::thread(cna).join(); // successful
}
EOT
xcrun clang -std=c++0x -stdlib=libc++ -c test.cpp

Output:
In file included from test.cpp:1:
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/../lib/c++/v1/thread:330:5: error:
    attempt to use a deleted function
  __invoke(_VSTD::move(_VSTD::get<0>(__t)), ...
  ^
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/../lib/c++/v1/thread:340:5: note:
    in instantiation of function template specialization
    'std::__1::__threaad_execute<CopyableNotAssignable, , >' requested here
  __threaad_execute(*__p, _Index());
  ^
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/../lib/c++/v1/thread:352:41: note:
    in instantiation of function template specialization
    'std::__1::__thread_proxy<std::__1::tuple<CopyableNotAssignable> >'
    requested here
  int __ec = pthread_create(&__t_, 0, &__thread_proxy<_Gp>, __p.get());
                                      ^
test.cpp:9:3: note: in instantiation of function template specialization
    'std::__1::thread::thread<CopyableNotAssignable, , void>' requested here
std::thread(CopyableNotAssignable(n)).join();
^
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/../lib/c++/v1/type_traits:825:5: note:
    function has been explicitly marked deleted here
  ~__nat() = delete;
  ^
1 error generated.

This test fails with libc++ std::thread. Is libc++ or the test in error?

  TEST(ThreadTests, InvalidNullJoin) {
    error_code ec;
    try {
      thread().join();
    } catch (system_error &se) {
      ec = se.code();
    }
    EXPECT_EQ(ec, errc::invalid_argument); // no_such_process w/ libc++
  }

N3242 §30.3.1.5 reads:

    void join();
    ...

    Error conditions:

      — resource_deadlock_would_occur — if deadlock is detected or
        this->get_id() == std::this_thread::get_id().

      - no_such_process — if the thread is not valid.

      - invalid_argument — if the thread is not joinable.

— Gordon

CopyableNotAssignable does not appear to be a callable object with zero parameters. LaunchByMove and LaunchByCopy should both fail. Or technically, they exhibit undefined behavior, so anything could happen. A compile-time error is the highest quality solution.

Howard

The thread is neither valid nor joinable. When I do this:

#include <thread>
#include <iostream>

int main()
{
    try
    {
        std::thread().join();
    }
    catch (const std::exception& e)
    {
        std::cout << e.what() << '\n';
    }
}

I get this:

thread::join failed: No such process

This is the error that the OS returns for this operation (join called on a non-existent thread). This looks conforming to me.

Howard

Thanks, Howard; that is an error in my reduction. The true test is this, and using 'bool *flag_;' instead of 'bool &flag_;' does make this case compile for reasons which are mysterious to me.

namespace {
  class SimpleJoinThread {
    bool &flag_;
  public:
    SimpleJoinThread(bool &flag) : flag_(flag) { }
    void operator()() { flag_ = true; }
  };
} // End anonymous namespace.

TEST(ThreadTests, SimpleJoin) {
  bool flag = false;
  std::thread(SimpleJoinThread(flag)).join();
  EXPECT_TRUE(flag);
}

There's something more complex going on in the compilation unit, though; the reduced case I presented does compile on its own with the addition of void operator()() {}.

Sorry for the noise.

— Gordon

Is this a correct translation of the test?

#include <thread>
#include <iostream>
#include <cassert>

namespace {
    class SimpleJoinThread {
        bool &flag_;
    public:
        SimpleJoinThread(bool &flag) : flag_(flag) { }
        void operator()() { flag_ = true; }
    };
} // End anonymous namespace.

int main()
{
    bool flag = false;
    std::thread(SimpleJoinThread(flag)).join();
    assert(flag);
}

The above is compiling and running for me (without assert). If it isn't for you, then perhaps we're looking at a libc++ or clang++ bug that has been recently fixed. But first let's zero in on a precise copy/pasteable test.

I ran this test with both:

clang++ -std=c++11 -stdlib=libc++ test.cpp

and

clang++ -std=c++03 -stdlib=libc++ test.cpp

Howard

Thanks, Howard; that is an error in my reduction. The true test is this, and using 'bool *flag_;' instead of 'bool &flag_;' does make this case compile for reasons which are mysterious to me.

namespace {
  class SimpleJoinThread {
    bool &flag_;
  public:
    SimpleJoinThread(bool &flag) : flag_(flag) { }
    void operator()() { flag_ = true; }
  };
} // End anonymous namespace.

TEST(ThreadTests, SimpleJoin) {
  bool flag = false;
  std::thread(SimpleJoinThread(flag)).join();
  EXPECT_TRUE(flag);
}

There's something more complex going on in the compilation unit, though; the reduced case I presented does compile on its own with the addition of void operator()() {}.

Is this a correct translation of the test?

#include <thread>
#include <iostream>
#include <cassert>

namespace {
   class SimpleJoinThread {
       bool &flag_;
   public:
       SimpleJoinThread(bool &flag) : flag_(flag) { }
       void operator()() { flag_ = true; }
   };
} // End anonymous namespace.

int main()
{
   bool flag = false;
   std::thread(SimpleJoinThread(flag)).join();
   assert(flag);
}

The above is compiling and running for me (without assert). If it isn't for you, then perhaps we're looking at a libc++ or clang++ bug that has been recently fixed.

Yes, that translation does work for me.

But first let's zero in on a precise copy/pasteable test.

I ran this test with both:

clang++ -std=c++11 -stdlib=libc++ test.cpp

and

clang++ -std=c++03 -stdlib=libc++ test.cpp

Agreed. I'm attempting to reduce a test case. The error log for the in situ failure is below.

— Gordon

In file included from .../ThreadTests.cpp:3:
In file included from ../../include/carbonite/compat/thread:17:
In file included from ../../include/carbonite/compat/emu/thread.h:8:
In file included from ../../include/carbonite/compat/chrono:8:
In file included from ../../include/carbonite/compat/emu/chrono.h:8:
In file included from ../../include/carbonite/compat/emu/chronofwd.h:8:
In file included from ../../include/carbonite/compat/ratio:8:
In file included from ../../include/carbonite/compat/emu/ratio.h:9:
In file included from ../../include/carbonite/compat/type_traits:15:
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/../lib/c++/v1/type_traits:3055:22: error: attempt to use a deleted function
    typedef decltype(swap(_VSTD::declval<_Tp&>(), _VSTD::declval<_Tp&>())) type;
                     ^
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/../lib/c++/v1/type_traits:3063:48: note: in instantiation of template class 'std::__1::__detail::__swappable<<anonymous>::SimpleJoinThread>' requested here
    : public integral_constant<bool, __detail::__swappable<_Tp>::value>
                                               ^
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/../lib/c++/v1/type_traits:3084:41: note: in instantiation of template class 'std::__1::__is_swappable<<anonymous>::SimpleJoinThread>' requested here
    : public __is_nothrow_swappable_imp<__is_swappable<_Tp>::value, _Tp>
                                        ^
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/../lib/c++/v1/tuple:217:16: note: in instantiation of template class 'std::__1::__is_nothrow_swappable<<anonymous>::SimpleJoinThread>' requested here
    _NOEXCEPT_(__is_nothrow_swappable<_Hp>::value)
               ^
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/../lib/c++/v1/__config:253:34: note: expanded from macro '_NOEXCEPT_'
# define _NOEXCEPT_(x) noexcept(x)
                                 ^
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/../lib/c++/v1/tuple:216:6: note: in instantiation of function template specialization 'std::__1::swap<0, <anonymous>::SimpleJoinThread, false>' requested here
void swap(__tuple_leaf<_Ip, _Hp, _Ep>& __x, __tuple_leaf<_Ip, _Hp, _Ep>& __y)
     ^
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/../lib/c++/v1/type_traits:3063:48: note: in instantiation of template class 'std::__1::__detail::__swappable<std::__1::__tuple_leaf<0, <anonymous>::SimpleJoinThread, false>>' requested here
    : public integral_constant<bool, __detail::__swappable<_Tp>::value>
                                               ^
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/../lib/c++/v1/type_traits:3084:41: note: (skipping 1 context in backtrace; use -ftemplate-backtrace-limit=0 to see all)
    : public __is_nothrow_swappable_imp<__is_swappable<_Tp>::value, _Tp>
                                        ^
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/../lib/c++/v1/tuple:332:44: note: in instantiation of template class 'std::__1::__is_nothrow_swappable<std::__1::__tuple_leaf<0, <anonymous>::SimpleJoinThread, false>>' requested here
    int swap(__tuple_leaf& __t) _NOEXCEPT_(__is_nothrow_swappable<__tuple_leaf>::value)
                                           ^
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/../lib/c++/v1/__config:253:34: note: expanded from macro '_NOEXCEPT_'
# define _NOEXCEPT_(x) noexcept(x)
                                 ^
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/../lib/c++/v1/tuple:436:14: note: in instantiation of template class 'std::__1::__tuple_leaf<0, <anonymous>::SimpleJoinThread, false>' requested here
    : public __tuple_leaf<_Indx, _Tp>...
             ^
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/../lib/c++/v1/tuple:523:10: note: in instantiation of template class 'std::__1::__tuple_impl<std::__1::__tuple_indices<0>, <anonymous>::SimpleJoinThread>' requested here
    base base_;
         ^
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/../lib/c++/v1/thread:350:36: note: in instantiation of template class 'std::__1::tuple<<anonymous>::SimpleJoinThread>' requested here
    _VSTD::unique_ptr<_Gp> __p(new _Gp(__decay_copy(_VSTD::forward<_Fp>(__f)),
                                   ^
.../ThreadTests.cpp:131:2: note: in instantiation of function template specialization 'std::__1::thread::thread<<anonymous>::SimpleJoinThread, , void>' requested here
        std::thread(SimpleJoinThread(flag)).join();
        ^
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/../lib/c++/v1/type_traits:825:5: note: function has been explicitly marked deleted here
    ~__nat() = delete;
    ^

Found it; the correct reduction has one additional line:

    #define decltype __typeof // CULPRIT

#ifdefing out that 'help' for clang permits the test to work without modification.

Reduction is below.

— Gordon

cat <<'EOT' >test.cpp
#define decltype __typeof // CULPRIT
#include <thread>
#include <iostream>
#include <cassert>

namespace {
   class SimpleJoinThread {
       bool &flag_;
   public:
       SimpleJoinThread(bool &flag) : flag_(flag) { }
       void operator()() { flag_ = true; }
   };
} // End anonymous namespace.

int main()
{
   bool flag = false;
   std::thread(SimpleJoinThread(flag)).join();
   assert(flag);
}
EOT

xcrun clang++ -std=c++0x -stdlib=libc++ test.cpp
./test

Interesting. Nice detective work.

I'm actually not duplicating your symptoms, but that may be because I currently have later versions of libc++ installed. Your detective work seems spot-on to me. test bug.

Fwiw, libc++ does a similar dance in C++03 mode. <__config> has:

#if !(__has_feature(cxx_decltype))
#define _LIBCPP_HAS_NO_DECLTYPE
#endif

// ...

#ifdef _LIBCPP_HAS_NO_DECLTYPE
#define decltype(x) __typeof__(x)
#endif

Howard

I do reproduce with the item from the previous e-mail. I thought for a moment I didn't, but it was because of an error I missed on the cat <<'EOT' >test.cpp:

    zsh: file exists: test.cpp

Anyhow, hopefully your explanation is right, and this is fixed in development. :slight_smile:

— Gordon

$ cat fail.cpp
#define decltype __typeof
#include <thread>

namespace {
  class SimpleJoinThread {
    bool &flag_;
  public:
    SimpleJoinThread(bool &flag) : flag_(flag) { }
    void operator()() { flag_ = true; }
  };
}

int main() {
  bool flag = false;
  std::thread(SimpleJoinThread(flag)).join();
}
$ diff -u fail.cpp pass.cpp
--- fail.cpp 2012-12-16 16:54:56.000000000 -0800
+++ pass.cpp 2012-12-16 16:55:46.000000000 -0800
@@ -1,4 +1,3 @@
-#define decltype __typeof
#include <thread>

namespace {
$ openssl sha1 *.cpp
SHA1(fail.cpp)= 21b5bbfdbf7bbc160efe531c443d9030664449b7
SHA1(pass.cpp)= a3d444faeebc43011d7ea674e048062029aa4719
$ xcrun clang++ -std=c++0x -stdlib=libc++ -o pass pass.cpp
$ ./pass
$ xcrun clang++ -std=c++0x -stdlib=libc++ -o fail fail.cpp
In file included from fail.cpp:2:
In file included from /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/../lib/c++/v1/thread:90:
In file included from /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/../lib/c++/v1/__functional_base:15:
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/../lib/c++/v1/type_traits:3055:22: error: attempt to use a deleted
      function
    typedef decltype(swap(_VSTD::declval<_Tp&>(), _VSTD::declval<_Tp&>())) type;
                     ^
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/../lib/c++/v1/type_traits:3063:48: note: in instantiation of template
      class 'std::__1::__detail::__swappable<<anonymous>::SimpleJoinThread>' requested here
    : public integral_constant<bool, __detail::__swappable<_Tp>::value>
                                               ^
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/../lib/c++/v1/type_traits:3084:41: note: in instantiation of template
      class 'std::__1::__is_swappable<<anonymous>::SimpleJoinThread>' requested here
    : public __is_nothrow_swappable_imp<__is_swappable<_Tp>::value, _Tp>
                                        ^
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/../lib/c++/v1/tuple:217:16: note: in instantiation of template class
      'std::__1::__is_nothrow_swappable<<anonymous>::SimpleJoinThread>' requested here
    _NOEXCEPT_(__is_nothrow_swappable<_Hp>::value)
               ^
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/../lib/c++/v1/__config:253:34: note: expanded from macro '_NOEXCEPT_'
# define _NOEXCEPT_(x) noexcept(x)
                                 ^
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/../lib/c++/v1/tuple:216:6: note: in instantiation of function template
      specialization 'std::__1::swap<0, <anonymous>::SimpleJoinThread, false>' requested here
void swap(__tuple_leaf<_Ip, _Hp, _Ep>& __x, __tuple_leaf<_Ip, _Hp, _Ep>& __y)
     ^
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/../lib/c++/v1/type_traits:3063:48: note: in instantiation of template
      class 'std::__1::__detail::__swappable<std::__1::__tuple_leaf<0, <anonymous>::SimpleJoinThread, false>>' requested here
    : public integral_constant<bool, __detail::__swappable<_Tp>::value>
                                               ^
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/../lib/c++/v1/type_traits:3084:41: note: (skipping 1 context in
      backtrace; use -ftemplate-backtrace-limit=0 to see all)
    : public __is_nothrow_swappable_imp<__is_swappable<_Tp>::value, _Tp>
                                        ^
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/../lib/c++/v1/tuple:332:44: note: in instantiation of template class
      'std::__1::__is_nothrow_swappable<std::__1::__tuple_leaf<0, <anonymous>::SimpleJoinThread, false>>' requested here
    int swap(__tuple_leaf& __t) _NOEXCEPT_(__is_nothrow_swappable<__tuple_leaf>::value)
                                           ^
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/../lib/c++/v1/__config:253:34: note: expanded from macro '_NOEXCEPT_'
# define _NOEXCEPT_(x) noexcept(x)
                                 ^
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/../lib/c++/v1/tuple:436:14: note: in instantiation of template class
      'std::__1::__tuple_leaf<0, <anonymous>::SimpleJoinThread, false>' requested here
    : public __tuple_leaf<_Indx, _Tp>...
             ^
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/../lib/c++/v1/tuple:523:10: note: in instantiation of template class
      'std::__1::__tuple_impl<std::__1::__tuple_indices<0>, <anonymous>::SimpleJoinThread>' requested here
    base base_;
         ^
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/../lib/c++/v1/thread:350:36: note: in instantiation of template class
      'std::__1::tuple<<anonymous>::SimpleJoinThread>' requested here
    _VSTD::unique_ptr<_Gp> __p(new _Gp(__decay_copy(_VSTD::forward<_Fp>(__f)),
                                   ^
fail.cpp:15:2: note: in instantiation of function template specialization 'std::__1::thread::thread<<anonymous>::SimpleJoinThread, , void>' requested here
        std::thread(SimpleJoinThread(flag)).join();
        ^
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/../lib/c++/v1/type_traits:825:5: note: function has been explicitly
      marked deleted here
    ~__nat() = delete;
    ^
1 error generated.
$

fail.cpp (297 Bytes)

pass.cpp (270 Bytes)