Reputation: 756
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
Reputation: 53175
std::thread
constructor via std::ref()
and std::cref()
wrappersWrap 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));
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?
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
orstd::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;
}
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
orstd::cref
).
Upvotes: 5
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