12
#include<iostream>
#include<thread>
using namespace std;

void f1(double& ret) {
   ret=5.;
}

int main() {
   double ret=0.;
   thread t1(f1, ret);
   t1.join();
   cout << "ret=" << ret << endl;
}

The above code fails compilation with the following error message:

g++ -std=c++14 -O2 -Wall -pedantic -pthread main.cpp && ./a.out
In file included from /usr/local/include/c++/5.3.0/thread:39:0,
                 from main.cpp:2:
/usr/local/include/c++/5.3.0/functional: In instantiation of 'struct std::_Bind_simple<void (*(double))(double&)>':
/usr/local/include/c++/5.3.0/thread:137:59:   required from 'std::thread::thread(_Callable&&, _Args&& ...) [with _Callable = void (&)(double&); _Args = {double&}]'
main.cpp:11:21:   required from here
/usr/local/include/c++/5.3.0/functional:1505:61: error: no type named 'type' in 'class std::result_of<void (*(double))(double&)>'
       typedef typename result_of<_Callable(_Args...)>::type result_type;
                                                             ^
/usr/local/include/c++/5.3.0/functional:1526:9: error: no type named 'type' in 'class std::result_of<void (*(double))(double&)>'
         _M_invoke(_Index_tuple<_Indices...>)
         ^

I understand that I can use std::ref() to pass the argument. But if I pass by value, why is it an error since thread should just copy the argument by value and pass some object stored inside thread to bind with the reference argument of function f1.

I feel that if I can understand what this result_of is doing and why it is giving error, I can better understand the reason. So could anyone walk me through the error msg? Especially the meanings of std::_Bind_simple<void (*(double))(double&)> and std::result_of<void (*(double))(double&)>.

EDIT: I know if I pass a value, the thread will only work on the copy and has no effect after the thread returns. That is not my concern. I want to know why it is giving error now, but it was not giving error to other posts on SO like the following:Difference between pointer and reference as thread parameter

Community
  • 1
  • 1
Rich
  • 1,669
  • 2
  • 19
  • 32

2 Answers2

16

I know if I pass a value, the thread will only work on the copy and has no effect after the thread returns.

No, that's not correct. The code should not silently make a copy and work on the copy, the standard says it must not even compile.

The standard requires that the arguments to the called function are copied (into storage managed by the C++ runtime) and then the copies are forwarded as rvalues. So in your example f1 gets passed an rvalue of type double and the parameter of type double& cannot bind to that rvalue.

The reason the standard requires that is so there is no silent copying and loss of data: if the function requires a modifiable reference then it won't compile unless you pass a reference explicitly using a reference_wrapper.

The compiler error you get involves result_of because that's how I made GCC's std::thread check if the function can be called with the supplied arguments. I use result_of<decltype(&f1)(double)> to check if the function pointer &f1 (which is of type void(*)(double&)) can be called with an rvalue of type double. It can't be called with an argument of that type, so the nested type result_of<decltype(&f1)(double)>::type is not defined, so the compiler says:

error: no type named 'type' in 'class std::result_of<void (*(double))(double&)>'

The error is a bit confusing because the C++ declarator rules mean that decltype(&f1)(double) gets displayed as void(*(double))(double&).

That is not my concern. I want to know why it is giving error now, but it was not giving error to other posts on SO

Those posts were using an old pre-C++11 or non-conforming compiler which didn't meet the requirements of the C++11 standard and incorrectly compiled the code.

Jonathan Wakely
  • 166,810
  • 27
  • 341
  • 521
  • from http://en.cppreference.com/w/cpp/thread/thread/thread, it clearly says it will copy/move the arguments to **thread-accessible storage** – Rich Mar 31 '16 at 19:22
  • 1
    So what? That doesn't mean it should compile. Make sure you have refreshed my answer to see the latest version which explains that the copies are forwarded as rvalues, so can't bind to non-const lvalue reference parameters. – Jonathan Wakely Mar 31 '16 at 19:24
  • Can you elaborate the `std::result_of` error? Is that trying to do what the standard is requiring as you said, ie pass the copies as rvalues? – Rich Mar 31 '16 at 19:24
  • The result_of error means that you cannot call a function of type `void(double&)` with an argument of type `double`, which is true, because you cannot bind a non-const lvalue reference to an rvalue. So you cannot construct a `std::thread` with those arguments, because calling `f1` with an rvalue won't compile. – Jonathan Wakely Mar 31 '16 at 19:25
  • I am kinda confused. Why the argument type of `double` is rvalue? – Rich Mar 31 '16 at 19:27
  • Ok. I saw your latest change about the copies being forwarded as rvalues. I am currently confused as to why `result_of` is error? Specifically what the `result_of` expression is trying to do? What is the difference between `double` with and without the reference specifier `&`. Can you elaborate that in your answer as I don't want to clutter the comments. Thanks! – Rich Mar 31 '16 at 19:34
  • See the latest update. With the `&` it's an lvalue reference. `result_of::type` tells you the type of calling an `F` with an rvalue of type `double` and `result_of::type` tells you the type of calling an `F` with an lvalue of type `double`. If you can't call `F` with an rvalue then the first one is not a valid type. If you can't call `F` with an lvalue then the second one is not a valid type. – Jonathan Wakely Mar 31 '16 at 19:45
  • Super clear! One last remaining puzzle: why not use `double&&` to indicate rvalue instead of plain `double`? Is there a `result_of::type`? – Rich Mar 31 '16 at 19:49
  • "The error is a bit confusing because" we should get damn concepts in the language, it is a pity that they did not make to standard again :( – Slava Mar 31 '16 at 20:03
  • In this context `result_of::type` is equivalent to `result_of::type`, both test whether the function can be called with an rvalue of type `double`. – Jonathan Wakely Mar 31 '16 at 20:10
3

Jonathan's answer is definitive. Time spent studying it would be time well spent.

In the meantime, modifying the code thus will do what you want - namely send a reference into the thread function:

thread t1(f1, std::ref(ret));
Richard Hodges
  • 68,278
  • 7
  • 90
  • 142