sunshilong369
sunshilong369

Reputation: 756

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()\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

Upvotes: 25

Views: 37095

Answers (2)

Gabriel Staples
Gabriel Staples

Reputation: 53175

Summary: how to properly pass references to the std::thread constructor via std::ref() and std::cref() wrappers

Wrap reference parameters with std::ref() and const reference parameters with std::cref():

// Function prototypes
void foo(Data& data);           // takes a reference parameter
void foo2(const Data& data);    // takes a const reference parameter

// std::thread constructor calls with proper `std::ref()` and `std::cref()` 
// wrappers
std::thread t1 = std::thread(foo, std::ref(data));
std::thread t2 = std::thread(foo2, std::cref(data));

Problem

I also couldn't seem to get reference parameters to work with the std::thread() constructor. I was about to make this a separate Q&A but discovered this question here instead. I'd like to post the full error output from g++ below to make this question more easily searchable.

My example:

#include <cstdint>  // For `uint8_t`, `int8_t`, etc.
#include <cstdio>   // For `printf()`
#include <iostream>  // For `std::cin`, `std::cout`, `std::endl`, etc.
#include <thread>

struct Data
{
    int i = 7;
};

// Accepts a reference
void foo(Data& data)
{
    printf("data.i = %i\n", data.i);
}

int main()
{
    printf("`std::thread` test\n");

    Data data;
    std::thread t1 = std::thread(foo, data);  // <========= here
    t1.join();

    return 0;
}

Sample build command and output:

eRCaGuy_hello_world/cpp$ g++ -Wall -Wextra -Werror -O3 -std=c++17 -pthread std_thread__pass_parameter_by_reference_to_constructor.cpp -o bin/a && bin/a
In file included from std_thread__pass_parameter_by_reference_to_constructor.cpp:87:
/usr/include/c++/8/thread: In instantiation of ‘std::thread::thread(_Callable&&, _Args&& ...) [with _Callable = void (&)(Data&); _Args = {Data&}; <template-parameter-1-3> = void]’:
std_thread__pass_parameter_by_reference_to_constructor.cpp:105:43:   required from here
/usr/include/c++/8/thread:120:17: error: static assertion failed: std::thread arguments must be invocable after conversion to rvalues
  static_assert( __is_invocable<typename decay<_Callable>::type,
                 ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
           typename decay<_Args>::type...>::value,
           ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/usr/include/c++/8/thread: In instantiation of ‘struct std::thread::_Invoker<std::tuple<void (*)(Data&), Data> >’:
/usr/include/c++/8/thread:132:22:   required from ‘std::thread::thread(_Callable&&, _Args&& ...) [with _Callable = void (&)(Data&); _Args = {Data&}; <template-parameter-1-3> = void]’
std_thread__pass_parameter_by_reference_to_constructor.cpp:105:43:   required from here
/usr/include/c++/8/thread:250:2: error: no matching function for call to ‘std::thread::_Invoker<std::tuple<void (*)(Data&), Data> >::_M_invoke(std::thread::_Invoker<std::tuple<void (*)(Data&), Data> >::_Indices)’
  operator()()
  ^~~~~~~~
/usr/include/c++/8/thread:241:4: note: candidate: ‘template<long unsigned int ..._Ind> decltype (std::__invoke((_S_declval<_Ind>)()...)) std::thread::_Invoker<_Tuple>::_M_invoke(std::_Index_tuple<_Ind ...>) [with long unsigned int ..._Ind = {_Ind ...}; _Tuple = std::tuple<void (*)(Data&), Data>]’
    _M_invoke(_Index_tuple<_Ind...>)
    ^~~~~~~~~
/usr/include/c++/8/thread:241:4: note:   template argument deduction/substitution failed:
/usr/include/c++/8/thread: In substitution of ‘template<long unsigned int ..._Ind> decltype (std::__invoke(_S_declval<_Ind>()...)) std::thread::_Invoker<std::tuple<void (*)(Data&), Data> >::_M_invoke<_Ind ...>(std::_Index_tuple<_Ind ...>) [with long unsigned int ..._Ind = {0, 1}]’:
/usr/include/c++/8/thread:250:2:   required from ‘struct std::thread::_Invoker<std::tuple<void (*)(Data&), Data> >’
/usr/include/c++/8/thread:132:22:   required from ‘std::thread::thread(_Callable&&, _Args&& ...) [with _Callable = void (&)(Data&); _Args = {Data&}; <template-parameter-1-3> = void]’
std_thread__pass_parameter_by_reference_to_constructor.cpp:105:43:   required from here
/usr/include/c++/8/thread:243:29: error: no matching function for call to ‘__invoke(std::__tuple_element_t<0, std::tuple<void (*)(Data&), Data> >, std::__tuple_element_t<1, std::tuple<void (*)(Data&), Data> >)’
    -> decltype(std::__invoke(_S_declval<_Ind>()...))
                ~~~~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~
In file included from /usr/include/c++/8/tuple:41,
                 from /usr/include/c++/8/bits/unique_ptr.h:37,
                 from /usr/include/c++/8/memory:80,
                 from /usr/include/c++/8/thread:39,
                 from std_thread__pass_parameter_by_reference_to_constructor.cpp:87:
/usr/include/c++/8/bits/invoke.h:89:5: note: candidate: ‘template<class _Callable, class ... _Args> constexpr typename std::__invoke_result<_Functor, _ArgTypes>::type std::__invoke(_Callable&&, _Args&& ...)’
     __invoke(_Callable&& __fn, _Args&&... __args)
     ^~~~~~~~
/usr/include/c++/8/bits/invoke.h:89:5: note:   template argument deduction/substitution failed:
/usr/include/c++/8/bits/invoke.h: In substitution of ‘template<class _Callable, class ... _Args> constexpr typename std::__invoke_result<_Functor, _ArgTypes>::type std::__invoke(_Callable&&, _Args&& ...) [with _Callable = void (*)(Data&); _Args = {Data}]’:
/usr/include/c++/8/thread:243:29:   required by substitution of ‘template<long unsigned int ..._Ind> decltype (std::__invoke(_S_declval<_Ind>()...)) std::thread::_Invoker<std::tuple<void (*)(Data&), Data> >::_M_invoke<_Ind ...>(std::_Index_tuple<_Ind ...>) [with long unsigned int ..._Ind = {0, 1}]’
/usr/include/c++/8/thread:250:2:   required from ‘struct std::thread::_Invoker<std::tuple<void (*)(Data&), Data> >’
/usr/include/c++/8/thread:132:22:   required from ‘std::thread::thread(_Callable&&, _Args&& ...) [with _Callable = void (&)(Data&); _Args = {Data&}; <template-parameter-1-3> = void]’
std_thread__pass_parameter_by_reference_to_constructor.cpp:105:43:   required from here
/usr/include/c++/8/bits/invoke.h:89:5: error: no type named ‘type’ in ‘struct std::__invoke_result<void (*)(Data&), Data>’

There's a lot to look at there, but the first part of the error that stuck out to me was:

/usr/include/c++/8/thread:120:17: error: static assertion failed: std::thread arguments must be invocable after conversion to rvalues

What's the problem?

Solution

You can't pass references directly to the std::thread() constructor. You must wrap them in std::ref().

See the std::thread::thread() constructor reference pg here: https://en.cppreference.com/w/cpp/thread/thread/thread:

The arguments to the thread function are moved or copied by value. If a reference argument needs to be passed to the thread function, it has to be wrapped (e.g., with std::ref or std::cref).

So, wrap reference parameters with std::ref() and const reference parameters with std::cref(), like this:

std::thread t1 = std::thread(foo, std::ref(data));
std::thread t2 = std::thread(foo2, std::cref(data));

Full example:

std_thread__pass_parameter_by_reference_to_constructor_via_std_ref.cpp:

// C & C++ includes
#include <cstdint>  // For `uint8_t`, `int8_t`, etc.
#include <cstdio>   // For `printf()`
#include <iostream>  // For `std::cin`, `std::cout`, `std::endl`, etc.
#include <thread>

struct Data
{
    int i = 7;
};

// Accepts a reference
void foo(Data& data)
{
    printf("data.i = %i\n", data.i);
}

// Accepts a const reference
void foo2(const Data& data)
{
    printf("data.i = %i\n", data.i);
}

// int main(int argc, char *argv[])  // alternative prototype
int main()
{
    printf("`std::thread` test\n");

    Data data;
    std::thread t1 = std::thread(foo, std::ref(data));
    std::thread t2 = std::thread(foo2, std::cref(data));
    t1.join();
    t2.join();

    return 0;
}

References

  1. Where I first learned this: C++11 std::thread accepting function with rvalue parameter; see my comments under this answer too
  2. https://en.cppreference.com/w/cpp/thread/thread/thread

    The arguments to the thread function are moved or copied by value. If a reference argument needs to be passed to the thread function, it has to be wrapped (e.g., with std::ref or std::cref).

Upvotes: 5

Jonathan Wakely
Jonathan Wakely

Reputation: 171433

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

Upvotes: 40

Related Questions