0

I'm having a hard time with the following std::thread's note (from cppref):

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).

Okay - since there's no guarantee that the argument will remain alive up until the end of the thread's execution, it makes sense that the thread itself should own it. Now, consider the following code snippet:

#include <iostream>
#include <thread>

void
tellValue(int& value)
{
    std::cout << "The value is: " << value << std::endl;
}

int
main()
{
    int mainThreadVariable{0};

    std::thread thr0{tellValue, mainThreadVariable};

    thr0.join();

    return 0;
}

Clang does not accept this, neither should it - the thread doesn't own mainThreadVariable. Why, however, does it accept this:

#include <iostream>
#include <thread>

int
main()
{
    int mainThreadVariable{0};

    std::thread thr0{[&]()
                     {
                         std::cout << "The value is: " << mainThreadVariable
                                   << std::endl;
                         ++mainThreadVariable;
                     }};

    thr0.join();

    std::cout << "The value is: " << mainThreadVariable << std::endl;

    return 0;
}

The latter outputs the following:

./PassingArgumentsToAThread 
The value is: 0
The value is: 1

The thread neither copied mainThreadVariable nor moved it, because it is safe and sound at the end of the main thread. The callable object captured it by reference, so why was this allowed?

2
  • Clang does not accept this Please post the error message. I know the issue and am pretty sure the answer is in the error message. Commented Aug 25, 2023 at 3:23
  • 1
    @xaxxon made it for you. must be invocable after conversion to rvalues - this means lvalue reference int& can't bind rvalue. Once you declare the value const int&, it can. Commented Aug 25, 2023 at 3:33

4 Answers 4

1

Okay - since there's no guarantee that the argument will remain alive up until the end of the thread's execution, it makes sense that the thread itself should own it.

That's not actually what cppreference is trying to tell you. It really comes down to how bindings work. The solution is already described to you: use a reference wrapper std::ref.

This works:

std::thread thr0{tellValue, std::ref(mainThreadVariable)};

The callable object captured it by reference, so why was this allowed?

Under the hood, that reference-wrapper approach is essentially what your lambda with auto capture-by-reference is doing, too. Probably. The compiler might be able to improve on that in certain cases, but that's an implementation detail.

Clang does not accept this, neither should it - the thread doesn't own mainThreadVariable.

In neither case does the thread "own" the referenced object. It simply has a means of accessing it, regardless of whether or not your program uses it safely.

Sign up to request clarification or add additional context in comments.

Comments

1

In your first example, while calling from main, you are passing mainThreadVariable to function as a value and and your function void tellValue(int& value) expect argument as an reference. Basically, arguments passed in constructor of std::thread are forwarded as rvalues to the function tellValue. If you expect arguments as reference in your tellValue. then c++ provides a way to make it as reference using std::ref().

Modified version of your first example

#include <iostream>
#include <thread>

using namespace std;


void tellValue(int& value)
{
    std::cout << "\nThe value inside thread: " << value << std::endl;
    ++value;
    value  = 2000;
}

int main()
{
    int mainThreadVariable{0};

    std::thread thr0{tellValue, ref(mainThreadVariable)};

    thr0.join();
    cout << "\nThe value in main: " << mainThreadVariable;

    return 0;
}

It Prints

The value inside thread: 0

The value in main: 2000

And your second example captures all variables from the main thread as reference in your lambda function and this values you can modify in your lambda which is running inside thread.

You can read about lambda references captures here: https://en.cppreference.com/w/cpp/language/lambda

1 Comment

When you provide a complete example, you should also add the necessary header file for std::ref, which is <functional>.
0

Your first example doesn't fail because of the compiler knowing the lifetime of variables. It fails for this very specific reason:

https://godbolt.org/z/aYWaTzMT7

**/opt/compiler-explorer/gcc-12.1.0/include/c++/12.1.0/bits/std_thread.h:129:72: error: static assertion failed: std::thread arguments must be invocable after conversion to rvalues **

The second version doesn't have this problem, so it's allowed.

The compiler cannot track variable scopes across threads and know what your program is supposed to do. That would essentially be solving the halting problem.

Comments

0

Because it's impossible for a template to analyze what kind of captures a lambda has.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.