Search code examples
c++multithreadingstdthread

When using std::thread class, why exactly can I pass lambda expression that capture variables by reference?


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?


Solution

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