Search code examples
c++multithreadingmutexfunctor

Why can't I pass in a functor with a mutex to a thread?


class test
{
    std::mutex m1;

public:
    inline static int i{0};
    void operator()()
    {
        m1.lock();
        ++i;
        m1.unlock();
    }
};


int main()
{
    test t;
    std::thread t1{t}; // doesn't work
    // std::thread t1{std::ref(t)}; // works

    t1.join();

    cout << test::i << endl;
}

Error:

In file included from test.cpp:19:
/Library/Developer/CommandLineTools/usr/include/c++/v1/thread:365:17: error: no matching constructor for initialization of
      '_Gp' (aka 'tuple<unique_ptr<std::__1::__thread_struct>, test>')
            new _Gp(std::move(__tsp),
                ^   ~~~~~~~~~~~~~~~~~
test.cpp:53:17: note: in instantiation of function template specialization 'std::__1::thread::thread<test &, void>' requested
      here
    std::thread t1{t}; // doesn't work
                ^
/Library/Developer/CommandLineTools/usr/include/c++/v1/tuple:625:5: note: candidate template ignored: requirement
      '__lazy_and<is_same<allocator_arg_t, unique_ptr<__thread_struct, default_delete<__thread_struct> > >,
      __lazy_all<__dependent_type<is_default_constructible<unique_ptr<__thread_struct, default_delete<__thread_struct> > >,
      true>, __dependent_type<is_default_constructible<test>, true> > >::value' was not satisfied [with _AllocArgT =
      std::__1::unique_ptr<std::__1::__thread_struct, std::__1::default_delete<std::__1::__thread_struct> >, _Alloc = test,
      _Dummy = true]
    tuple(_AllocArgT, _Alloc const& __a)
    ^
/Library/Developer/CommandLineTools/usr/include/c++/v1/tuple:641:5: note: candidate template ignored: requirement
      '_CheckArgsConstructor<true>::template __enable_implicit<const std::__1::unique_ptr<std::__1::__thread_struct,
      std::__1::default_delete<std::__1::__thread_struct> > &, const test &>()' was not satisfied [with _Dummy = true]
    tuple(const _Tp& ... __t) _NOEXCEPT_((__all<is_nothrow_copy_constructible<_Tp>::value...>::value))
    ^
/Library/Developer/CommandLineTools/usr/include/c++/v1/tuple:659:14: note: candidate template ignored: requirement
      '_CheckArgsConstructor<true>::template __enable_explicit<const std::__1::unique_ptr<std::__1::__thread_struct,
      std::__1::default_delete<std::__1::__thread_struct> > &, const test &>()' was not satisfied [with _Dummy = true]
    explicit tuple(const _Tp& ... __t) _NOEXCEPT_((__all<is_nothrow_copy_constructible<_Tp>::value...>::value))
             ^
/Library/Developer/CommandLineTools/usr/include/c++/v1/tuple:723:9: note: candidate template ignored: requirement
      '_CheckArgsConstructor<sizeof...(_Up) == sizeof...(_Tp) && !false>::template
      __enable_implicit<std::__1::unique_ptr<std::__1::__thread_struct, std::__1::default_delete<std::__1::__thread_struct> >,
      test>() || _CheckArgsConstructor<_EnableImplicitReducedArityExtension && sizeof...(_Up) < sizeof...(_Tp) &&
      !false>::template __enable_implicit<std::__1::unique_ptr<std::__1::__thread_struct,
      std::__1::default_delete<std::__1::__thread_struct> >, test>()' was not satisfied [with _Up =
      <std::__1::unique_ptr<std::__1::__thread_struct, std::__1::default_delete<std::__1::__thread_struct> >, test>,
      _PackIsTuple = false]
        tuple(_Up&&... __u)
        ^
/Library/Developer/CommandLineTools/usr/include/c++/v1/tuple:756:9: note: candidate template ignored: requirement
      '_CheckArgsConstructor<sizeof...(_Up) <= sizeof...(_Tp) && !_PackExpandsToThisTuple<unique_ptr<__thread_struct,
      default_delete<__thread_struct> >, test>::value>::template
      __enable_explicit<std::__1::unique_ptr<std::__1::__thread_struct, std::__1::default_delete<std::__1::__thread_struct> >,
      test>() || _CheckArgsConstructor<!_EnableImplicitReducedArityExtension && sizeof...(_Up) < sizeof...(_Tp) &&
      !_PackExpandsToThisTuple<unique_ptr<__thread_struct, default_delete<__thread_struct> >, test>::value>::template
      __enable_implicit<std::__1::unique_ptr<std::__1::__thread_struct, std::__1::default_delete<std::__1::__thread_struct> >,
      test>()' was not satisfied [with _Up = <std::__1::unique_ptr<std::__1::__thread_struct,
      std::__1::default_delete<std::__1::__thread_struct> >, test>]
        tuple(_Up&&... __u)
        ^
/Library/Developer/CommandLineTools/usr/include/c++/v1/tuple:783:9: note: candidate template ignored: requirement
      '_CheckArgsConstructor<sizeof...(_Up) == sizeof...(_Tp) && !_PackExpandsToThisTuple<>::value>::template
      __enable_implicit<>()' was not satisfied [with _Alloc = test, _Up = <>]
        tuple(allocator_arg_t, const _Alloc& __a, _Up&&... __u)
        ^
/Library/Developer/CommandLineTools/usr/include/c++/v1/tuple:803:9: note: candidate template ignored: requirement
      '_CheckArgsConstructor<sizeof...(_Up) == sizeof...(_Tp) && !_PackExpandsToThisTuple<>::value>::template
      __enable_explicit<>()' was not satisfied [with _Alloc = test, _Up = <>]
        tuple(allocator_arg_t, const _Alloc& __a, _Up&&... __u)
        ^
/Library/Developer/CommandLineTools/usr/include/c++/v1/tuple:612:23: note: candidate constructor template not viable: requires
      0 arguments, but 2 were provided
    _LIBCPP_CONSTEXPR tuple()
                      ^
/Library/Developer/CommandLineTools/usr/include/c++/v1/tuple:677:7: note: candidate constructor template not viable: requires 4
      arguments, but 2 were provided
      tuple(allocator_arg_t, const _Alloc& __a, const _Tp& ... __t)
      ^
/Library/Developer/CommandLineTools/usr/include/c++/v1/tuple:697:7: note: candidate constructor template not viable: requires 4
      arguments, but 2 were provided
      tuple(allocator_arg_t, const _Alloc& __a, const _Tp& ... __t)
      ^
/Library/Developer/CommandLineTools/usr/include/c++/v1/tuple:822:9: note: candidate constructor template not viable: requires
      single argument '__t', but 2 arguments were provided
        tuple(_Tuple&& __t) _NOEXCEPT_((is_nothrow_constructible<_BaseT, _Tuple>::value))
        ^
/Library/Developer/CommandLineTools/usr/include/c++/v1/tuple:837:9: note: candidate constructor template not viable: requires
      single argument '__t', but 2 arguments were provided
        tuple(_Tuple&& __t) _NOEXCEPT_((is_nothrow_constructible<_BaseT, _Tuple>::value))
        ^
/Library/Developer/CommandLineTools/usr/include/c++/v1/tuple:850:9: note: candidate constructor template not viable: requires 3
      arguments, but 2 were provided
        tuple(allocator_arg_t, const _Alloc& __a, _Tuple&& __t)
        ^
/Library/Developer/CommandLineTools/usr/include/c++/v1/tuple:864:9: note: candidate constructor template not viable: requires 3
      arguments, but 2 were provided
        tuple(allocator_arg_t, const _Alloc& __a, _Tuple&& __t)
        ^
/Library/Developer/CommandLineTools/usr/include/c++/v1/tuple:615:5: note: candidate constructor not viable: requires 1
      argument, but 2 were provided
    tuple(tuple const&) = default;
    ^
In file included from test.cpp:1:
In file included from /Library/Developer/CommandLineTools/usr/include/c++/v1/iostream:38:
In file included from /Library/Developer/CommandLineTools/usr/include/c++/v1/ios:216:
In file included from /Library/Developer/CommandLineTools/usr/include/c++/v1/__locale:15:
In file included from /Library/Developer/CommandLineTools/usr/include/c++/v1/string:500:
In file included from /Library/Developer/CommandLineTools/usr/include/c++/v1/string_view:176:
In file included from /Library/Developer/CommandLineTools/usr/include/c++/v1/__string:56:
In file included from /Library/Developer/CommandLineTools/usr/include/c++/v1/algorithm:640:
In file included from /Library/Developer/CommandLineTools/usr/include/c++/v1/initializer_list:47:
In file included from /Library/Developer/CommandLineTools/usr/include/c++/v1/cstddef:110:
/Library/Developer/CommandLineTools/usr/include/c++/v1/type_traits:2360:12: error: call to implicitly-deleted copy constructor
      of 'typename decay<test &>::type' (aka 'test')
    return _VSTD::forward<_Tp>(__t);
           ^~~~~~~~~~~~~~~~~~~~~~~~
/Library/Developer/CommandLineTools/usr/include/c++/v1/__config:508:15: note: expanded from macro '_VSTD'
#define _VSTD std::_LIBCPP_NAMESPACE
              ^
/Library/Developer/CommandLineTools/usr/include/c++/v1/thread:366:21: note: in instantiation of function template
      specialization 'std::__1::__decay_copy<test &>' requested here
                    __decay_copy(_VSTD::forward<_Fp>(__f)),
                    ^
test.cpp:53:17: note: in instantiation of function template specialization 'std::__1::thread::thread<test &, void>' requested
      here
    std::thread t1{t}; // doesn't work
                ^
test.cpp:36:16: note: copy constructor of 'test' is implicitly deleted because field 'm1' has an inaccessible copy constructor
    std::mutex m1;
               ^
2 errors generated.

This program fails to compile when I pass in the functor into the thread. But it works when I wrap it with std::ref. It appears the class member mutex is the issue, but I am not sure why. Can someone explain why the std::ref wrapper allows this to compile but without it, the program doesn't compile?

The compiler error message doesn't seem to help.


Solution

  • The default behaviour of threads is to make copies. Among other things, this prevents the general nastiness of having to synchronize the data across threads and the data going out of scope before the thread completes.

    In this specific case, the object being copied contains a mutex, and mutexs cannot be copied or moved. A mutex keeps multiple threads out of the same critical section of code. If different threads have different copies of the mutex, they can all lock their copy and enter the critical section, rendering the mutex useless. They must all share the same mutex, and in this case this means all threads must all share the same test.

    In this case a static mutex m1 would be a viable solution in addition to passing by reference.

    Note: Because i is public and accessible to anyone, i can easily be accessed by anyone regardless of the mutex.

    Note: Prefer to use a std::lock_guard or a std::scoped_lock instead of manually calling lock and unlock manually. A lock_guard or scoped_lock locks the mutex on construction and unlocks on destruction, guaranteeing the mutex will be unlocked when the locking object goes out of scope.

    Note: Removing the mutexand using std::atomic<int> in place of int should solve most synchronization problems.