Welcome to WuJiGu Developer Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
3.8k views
in Technique[技术] by (71.8m points)

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

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()
"; }
    // Note: non-virtual destructor is OK here
    ~Base() { std::cout << "  Base::~Base()
"; }
};

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

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:
"
                  << "  lp.get() = " << lp.get()
                  << ", lp.use_count() = " << lp.use_count() << '
';
    }
}

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

    std::cout << "Created a shared Derived (as a pointer to Base)
"
              << "  p.get() = " << p.get()
              << ", p.use_count() = " << p.use_count() << '
';
    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
"
              << "ownership from main:
"
              << "  p.get() = " << p.get()
              << ", p.use_count() = " << p.use_count() << '
';
    t1.join(); t2.join(); t3.join();

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

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
See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Answer

0 votes
by (71.8m points)

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


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome to WuJiGu Developer Q&A Community for programmer and developer-Open, Learning and Share
...