Search code examples
c++thread-safetystdatomic

std::atomic passed as a const reference to a non-atomic type


Let's say I have this application:

#include <atomic>
#include <thread>
#include <iostream>
#include <chrono>

void do_something(const std::atomic<bool>& stop) {
    while (!stop) {
        std::cout << "Doing stuff..." << std::endl;
        std::this_thread::sleep_for(std::chrono::seconds(1));
    }
}

int main() {
    std::atomic<bool> stop { false };

    std::thread wait([&stop] { 
        std::this_thread::sleep_for(std::chrono::seconds(10));
        stop = true;
    });

    do_something(stop);

    wait.join();
}

This works as expected. The main thread loops until the wait thread sets stop. As far as I know, there's no real issues with this application since it's using an std::atomic<bool> for synchronization between the threads.

However, I can break the application by changing the signature of do_something to this:

void do_something(const bool& stop);

This still compiles without any warnings, but the loop in do_something never exits. Conceptually, I understand why this happens. do_something is accepting a const reference to a non-atomic value, so it would be reasonable to optimize the function to something like:

void do_something(const bool& stop) {
    if (!stop) {
        while(true) {
            std::cout << "Doing stuff..." << std::endl;
            std::this_thread::sleep_for(std::chrono::seconds(1));
        }
    }
}

What's confusing to me is why the modified application compiles at all. I wouldn't expect to be able to pass an std::atomic value into a function expecting a const reference to a non-atomic type. I haven't looked at the standard, but I don't see anything on cppreference that suggests this conversion is allowed.

Am I misunderstanding something here? Is this just some quirk of std::atomic that's not obvious to me from the documentation?


Solution

  • This is because there is an implicit conversion when you call do_something(const bool &stop) while passing an std::atomic<bool>

    It translates to:

    do_something(static_cast<bool>(stop.operator bool()));

    As you can see here : https://en.cppreference.com/w/cpp/atomic/atomic/operator_T

    You actually tell the compiler to load the value once, right at the time of the call.