(1) I've this code snippet:
void tByRef(int& i) {
++i;
}
int main() {
int i = 0;
tByRef(i); // ok
thread t1(tByRef, i); // fail to compile
thread t2(tByRef, (int&)i); // fail to compile
thread t3(tByRef, ref(i)); // ok
return 0;
}
As you could see, function tByRef
accepts a lvalue reference as parameter to change the i
value. So calling it directly tByRef(i)
passes compilation.
But when I try to do same thing for thread function call, e.g. thread t1(tByRef, i)
, it fails to compile. Only when I added ref()
around i
, then it gets compiled.
Why need extra ref
call here? If this is required for passing by reference, then how to explain that tByRef(i)
gets compiled?
(2) I then changed tByRef to be template
function with &&
parameter, this time, even t3
fails to compile:
template<typename T>
void tByRef(T&& i) {
++i;
}
This &&
in template parameter type is said to be reference collapse which could accept both lvalue and rvalue reference. Why in my sample code, t1, t2, t3 all fails to compile to match it?
Thanks.
Threads execute asynchronously from the code that started them. That's kind of the point. This means that, when a thread function actually gets called, the code that started the thread may well have left that callstack. If the user passed a reference to a local variable, that variable may be off the stack by the time the thread function gets called. Basically, passing by reference to a thread function is highly dangerous.
However, in C++, passing a variable by reference is trivial; you just provide the name to the function that takes its parameter by reference. Since it is so dangerous in this particular case, std::thread
takes steps to prevent you from doing it.
All arguments to the thread function are copied/moved into internal storage when the thread
object is created, and your thread function's parameters are initialized from those copies.
Now, thread
could initialize non-const
lvalue reference parameters with a reference to the internal object for that parameter. However, a function which specifically takes a non-const
lvalue reference is almost always a function that is expected to modify this value in a way that will be visible to others. But... it won't be visible to anyone, because it will be given a reference to an object stored internally in the thread
that is accessible to no one else.
In short, whatever you thought was going to happen will not happen. Hence the compile error: thread
is specifically designed to detect this circumstance and assume that you've made some kind of mistake.
However, while non-const
lvalue reference parameters are inherently dangerous, they can still be useful. So std::ref
is used as a way for a user to explicitly ask to pass a reference parameter.
As for why it fails to compile in your second example, tByRef
in this case is not the name of a function. It is the name of a template. std::thread
expects to be given a value which it can call. A template is not a value, nor is it convertible to a value.
A function template is a construct which generates a function when provided with template parameters. The template name alone is not a function.