Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why the compiler complains that std::thread arguments must be invocable after conversion to rvalues?

Tags:

c++

shared-ptr

Why the compiler complains if the the thread function delaration is changed to void thr(std::shared_ptr<Base>& p).Complie error:

gcc-10.1.0/include/c++/10.1.0/thread: In instantiation of 'std::thread::thread(_Callable&&, _Args&& ...) [with _Callable = void (&)(std::shared_ptr&); _Args = {std::shared_ptr&}; = void]': gcc-10.1.0/include/c++/10.1.0/thread:136:44: error: static assertion failed: std::thread arguments must be invocable after conversion to rvalues

136 | typename decay<_Args>::type...>::value,

Can someone explain me, step by step.

I would be grateful for any hint on this question.

#include <iostream>
#include <memory>
#include <thread>
#include <chrono>
#include <mutex>

struct Base
{
    Base() { std::cout << "  Base::Base()\n"; }
    // Note: non-virtual destructor is OK here
    ~Base() { std::cout << "  Base::~Base()\n"; }
};

struct Derived: public Base
{
    Derived() { std::cout << "  Derived::Derived()\n"; }
    ~Derived() { std::cout << "  Derived::~Derived()\n"; }
};

void thr(std::shared_ptr<Base> p)
{
    std::this_thread::sleep_for(std::chrono::seconds(1));
    std::shared_ptr<Base> lp = p; // thread-safe, even though the
                                  // shared use_count is incremented
    {
        static std::mutex io_mutex;
        std::lock_guard<std::mutex> lk(io_mutex);
        std::cout << "local pointer in a thread:\n"
                  << "  lp.get() = " << lp.get()
                  << ", lp.use_count() = " << lp.use_count() << '\n';
    }
}

int main()
{
    std::shared_ptr<Base> p = std::make_shared<Derived>();

    std::cout << "Created a shared Derived (as a pointer to Base)\n"
              << "  p.get() = " << p.get()
              << ", p.use_count() = " << p.use_count() << '\n';
    std::thread t1(thr, p), t2(thr, p), t3(thr, p);
    p.reset(); // release ownership from main
    std::cout << "Shared ownership between 3 threads and released\n"
              << "ownership from main:\n"
              << "  p.get() = " << p.get()
              << ", p.use_count() = " << p.use_count() << '\n';
    t1.join(); t2.join(); t3.join();

    std::cout << "after joining the threads\n" <<
     "  p.get() = " << p.get() << ", p.use_count() " <<p.use_count() << std::endl;
    std::cout << "All threads completed, the last one deleted Derived\n";
}

The outputs:

Base::Base()
  Derived::Derived()
Created a shared Derived (as a pointer to Base)
  p.get() = 0x57be80, p.use_count() = 1
Shared ownership between 3 threads and released
ownership from main:
  p.get() = 0, p.use_count() = 0
local pointer in a thread:
  lp.get() = 0x57be80, lp.use_count() = 4  
local pointer in a thread:
  lp.get() = 0x57be80, lp.use_count() = 3
local pointer in a thread:
  lp.get() = 0x57be80, lp.use_count() = 2
  Derived::~Derived()
  Base::~Base()
after joining the threads
  p.get() = 0, p.use_count() 0
All threads completed, the last one deleted Derived
like image 904
sunshilong369 Avatar asked Dec 11 '22 00:12

sunshilong369


1 Answers

The arguments passed to the std::thread constructor will be copied and then forwarded as rvalues to the function that runs in the new thread. So when you create a std::thread like this:

std::thread t1(thr, p)

the argument p will be copied, then forwarded as an rvalue. If the function thr expects an lvalue reference then it can't be called with an rvalue.

The static assertion is telling that you that you can't call thr(shared_ptr<Base>&) with an rvalue shared_ptr<Base>. (Before I added the static assertion you just got a horrible template instantiation error from deep inside the guts of std::thread, now the idea is that it tells you what's wrong in English).

The solution to passing a reference into the function is to use the std::ref function to create a reference_wrapper object:

std::thread t1(thr, std::ref(p))

This will create a std::reference_wrapper<std::shared_ptr<Base>> which gets copied and forwarded to thr as an rvalue, and then that rvalue can be converted to shared_ptr<Base>& to initialize the parameter of the thr function.

This is also clearly explained at https://en.cppreference.com/w/cpp/thread/thread/thread#Notes

like image 96
Jonathan Wakely Avatar answered Dec 12 '22 12:12

Jonathan Wakely