std::thread::thread(3)

πŸ“¦ version 2024.07.05, in nixpkgs unstable, opensuse-leap 16.0

πŸ’… std::thread::thread β€” from the C++ Standard Library manual, bestie

Okay so this page is literally all about std::thread::thread, the constructor of the thread class, and it's from the C++ Standard Library section β€” welcome to the group chat 🫢

✨ NAME ✨

std::thread::thread β€” yeah it's the constructor, it's the main character, it constructs threads πŸ’–

πŸ¦‹ SYNOPSIS πŸ¦‹

Here's the whole lineup, four overloads, no notes:

thread() noexcept;                        (1) (since C++11)
thread( thread&& other ) noexcept;        (2) (since C++11)
template< class F, class... Args >        (3) (since C++11)
explicit thread( F&& f, Args&&... args );
thread( const thread& ) = delete;         (4) (since C++11)

Constructs a new std::thread object. Slay ✨

🌸 DESCRIPTION 🌸

Overload (1) β€” the empty one

Creates a new std::thread object which does not represent a thread. It's giving "placeholder queen" β€” it exists, it just has no thread behind it πŸ’€

Overload (2) β€” the move constructor

Move constructor. Constructs the std::thread object to represent the thread of execution that was represented by other. After this call other no longer represents a thread of execution. So other is literally left with nothing β€” not me stealing your thread 😭

Overload (3) β€” the real one, the icon

Creates a new std::thread object and associates it with a thread of execution. The new thread of execution starts executing:

INVOKE(decay-copy(std::forward<F>(f)),          (until C++23)
       decay-copy(std::forward<Args>(args))...)
std::invoke(auto(std::forward<F>(f)),           (since C++23)
            auto(std::forward<Args>(args))...)

The calls of decay-copy are evaluated (until C++23) / the values produced by auto are materialized (since C++23) in the current thread, so that any exceptions thrown during evaluation and copying/moving of the arguments are thrown in the current thread, without starting the new thread. Translation: the error happens on your thread before the new one even gets out of bed πŸ‘‘

This overload participates in overload resolution only if std::decay<F>::type (until C++20) / std::remove_cvref_t<F> (since C++20) is not the same type as std::thread.

If any of the following conditions is satisfied, the program is ill-formed:

  • F is not MoveConstructible. (until C++20)
  • Any type in Args is not MoveConstructible.
  • INVOKE(decay-copy(std::forward<F>(f)), decay-copy(std::forward<Args>(args))...) is not a valid expression.

If any of the following is false, the program is ill-formed (since C++20):

  • std::is_constructible_v<std::decay_t<F>, F>
  • (std::is_constructible_v<std::decay_t<Args>, Args> && ...)
  • std::is_invocable_v<std::decay_t<F>, std::decay_t<Args>...>

The completion of the invocation of the constructor synchronizes with the beginning of the invocation of the copy of f on the new thread of execution. (Synchro, but make it threading 🧚)

Overload (4) β€” deleted, we don't do that here

The copy constructor is deleted; threads are not copyable. No two std::thread objects may represent the same thread of execution. Periodt. One thread, one owner, that's the vibe check πŸ’…

πŸŽ€ PARAMETERS πŸŽ€

  • other β€” another thread object to construct this thread object with
  • f β€” Callable object to execute in the new thread
  • args β€” arguments to pass to the new function

πŸ’œ POSTCONDITIONS πŸ’œ

  • (1) get_id() equal to std::thread::id() (i.e. joinable() is false).
  • (2) other.get_id() equal to std::thread::id() and get_id() returns the value of other.get_id() prior to the start of construction.
  • (3) get_id() not equal to std::thread::id() (i.e. joinable() is true).

πŸ”₯ EXCEPTIONS πŸ”₯

  • (3) std::system_error if the thread could not be started. The exception may represent the error condition std::errc::resource_unavailable_try_again or another implementation-specific error condition. Not the vibe, but good to know 😭

⭐ NOTES ⭐

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). Learn it, live it, love it β€” this is the #1 thing people get wrong, fr fr πŸ™ˆ

Any return value from the function is ignored. If the function throws an exception, std::terminate is called. In order to pass return values or exceptions back to the calling thread, std::promise or std::async may be used. So no, you can't just return from a thread and expect it back β€” use the right tool bestie πŸ“

🌈 EXAMPLE 🌈

// Run this code

#include <chrono>
#include <iostream>
#include <thread>
#include <utility>

void f1(int n)
{
    for (int i = 0; i < 5; ++i)
    {
        std::cout << "Thread 1 executing\n";
        ++n;
        std::this_thread::sleep_for(std::chrono::milliseconds(10));
    }
}

void f2(int& n)
{
    for (int i = 0; i < 5; ++i)
    {
        std::cout << "Thread 2 executing\n";
        ++n;
        std::this_thread::sleep_for(std::chrono::milliseconds(10));
    }
}

class foo
{
public:
    void bar()
    {
        for (int i = 0; i < 5; ++i)
        {
            std::cout << "Thread 3 executing\n";
            ++n;
            std::this_thread::sleep_for(std::chrono::milliseconds(10));
        }
    }
    int n = 0;
};

class baz
{
public:
    void operator()()
    {
        for (int i = 0; i < 5; ++i)
        {
            std::cout << "Thread 4 executing\n";
            ++n;
            std::this_thread::sleep_for(std::chrono::milliseconds(10));
        }
    }
    int n = 0;
};

int main()
{
    int n = 0;
    foo f;
    baz b;
    std::thread t1; // t1 is not a thread
    std::thread t2(f1, n + 1); // pass by value
    std::thread t3(f2, std::ref(n)); // pass by reference
    std::thread t4(std::move(t3)); // t4 is now running f2(). t3 is no
       longer a thread
    std::thread t5(&foo::bar, &f); // t5 runs foo::bar() on object f
    std::thread t6(b); // t6 runs baz::operator() on a copy of object b
    t2.join();
    t4.join();
    t5.join();
    t6.join();
    std::cout << "Final value of n is " << n << '\n';
    std::cout << "Final value of f.n (foo::n) is " << f.n << '\n';
    std::cout << "Final value of b.n (baz::n) is " << b.n << '\n';
}

Possible output:

Thread 1 executing
Thread 2 executing
Thread 3 executing
Thread 4 executing
Thread 3 executing
Thread 1 executing
Thread 2 executing
Thread 4 executing
Thread 2 executing
Thread 3 executing
Thread 1 executing
Thread 4 executing
Thread 3 executing
Thread 2 executing
Thread 1 executing
Thread 4 executing
Thread 3 executing
Thread 1 executing
Thread 2 executing
Thread 4 executing
Final value of n is 5
Final value of f.n (foo::n) is 5
Final value of b.n (baz::n) is 0

(Yes the order looks chaotic β€” it's threads, that's the whole point, the interleaving is a ✨possible✨ output, not a promise 😌)

🧚 DEFECT REPORTS 🧚

The following behavior-changing defect reports were applied retroactively to previously published C++ standards. So old standard, new rules β€” glow-up via paperwork πŸ’…

  • LWG 2097 β€” applied to C++11. Behavior as published: for overload (3), F could be std::thread. Correct behavior: F is constrained.
  • LWG 3476 β€” applied to C++20. Behavior as published: overload (3) directly required (the decayed types of) F and the argument types to be move constructible. Correct behavior: removed these requirements^[1].
  1. ^ The move-constructibility is already indirectly required by std::is_constructible_v.

πŸ‘‘ REFERENCES πŸ‘‘

  • C++23 standard (ISO/IEC 14882:2023):
    • 33.4.3.3 thread constructors [thread.thread.constr]
  • C++20 standard (ISO/IEC 14882:2020):
    • 32.4.2.2 thread constructors [thread.thread.constr]
  • C++17 standard (ISO/IEC 14882:2017):
    • 33.3.2.2 thread constructors [thread.thread.constr]
  • C++14 standard (ISO/IEC 14882:2014):
    • 30.3.1.2 thread constructors [thread.thread.constr]
  • C++11 standard (ISO/IEC 14882:2011):
    • 30.3.1.2 thread constructors [thread.thread.constr]

πŸ’– SEE ALSO πŸ’–

  • constructor β€” constructs new jthread object (public member function of std::jthread)
  • C documentation for thrd_create

Okay that's the whole page, no cap πŸ’– Big shoutout to cppreference.com, the 2024.06.10 edition β€” that's the source and date of this iconic little document, so go give it a kiss on the forehead for me. Bye bestie, go join() your threads πŸŽ€βœ¨

πŸ“¦ where this page comes from