C++ Logo

std-discussion

Advanced search

P3111 atomic modify-write operations and forward progress

From: Nate Eldredge <nate_at_[hidden]>
Date: Thu, 4 Dec 2025 05:08:53 +0000
P3111 (https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2025/p3111r8.html) introduced atomic modify-write operations (previously called atomic reductions), which have weaker ordering than read-modify-write operations, and allow certain additional optimizations. The proposal has been adopted into the current C++26 drafts. Most of it makes sense to me, but its changes to the forward progress rules seem strange.

Specifically, in intro.progress p1.5 (https://eel.is/c++draft/intro.progress#1.5), modify-writes are excluded from the operations that count as forward progress. As such, it seems that an infinite loop which performs modify-writes, but none of the other operations on that list, causes undefined behavior.

So consider threads doing the following:

std::atomic<int> snarks_found;

void bellman() {
    snark s;
    do {
        s = hunt();
        snarks_found.store_add(1);
    } while (!s.is_boojum());
}

assuming that hunt() and is_boojum() are pure functions that return in finite time, and that we don't know statically whether any boojums exist. This code seems like an ideal candidate for `store_add`, but it results in UB if it turns out that no snark is a boojum. That seems very counterintuitive. In contrast, using `fetch_add` instead would be perfectly fine and behave as intended.

A main purpose of [intro.progress p1] is to allow the compiler to delete or transform loops that have no visible effect, even if it cannot prove that they terminate. But excluding modify-writes doesn't seem to further this goal. In the `bellman()` function above, the compiler still can't delete the loop, because if it turns out that some snark *is* a boojum, it has to ensure that `snarks_found` got incremented the right number of times; that's a visible effect.

The only gain I can see is that if the compiler can prove that the loop *is* always infinite (i.e. that no snark is ever a boojum), it can treat the entire function `bellman()` as unreachable, and omit its code at some savings in space. But that doesn't seem especially valuable, particularly as it seems very unlikely to be what the programmer intends.

So I'd like to understand if there are other benefits of excluding modify-writes in [intro.progress p1], that would justify what seems like a violation of the principle of least surprise.

The discussion in P3111 doesn't seem to contain any clues. Indeed, the changelog entry for R1 suggests that SG1 favored the proposition "Reduction operations are not a step, but infinite loops around reduction operations are not UB", 3-9-1-0-0. There's no indication as to why they changed their mind on the latter point, and made such loops UB after all.

As a side note, I also don't understand what's achieved by stating in p3.4 that modify-write operations are not an execution step for threads with progress guarantees. With p1.5 as it stands, it seems redundant, because a well-defined program can only execute finitely many modify-writes before getting to some other operation that is a step. And if modify-writes weren't excluded from p1.5 (as per the R1 poll), then `bellman()` is always well-defined and must execute as written, which creates an impossibility: if no snark is a boojum, it will never execute a step, because `store_add` isn't one and the loop doesn't contain any others, yet p3 says it must eventually execute a step.

Can anyone shed any light on this?

Thanks.

Received on 2025-12-04 05:08:56