C++ Logo

std-discussion

Advanced search

Re: Why are P3111 modify-write operations excluded from forward progress?

From: Lénárd Szolnoki <cpp_at_[hidden]>
Date: Sun, 28 Sep 2025 07:55:48 +0100
Hi,

On 28 September 2025 06:27:26 BST, Nate Eldredge via Std-Discussion <std-discussion_at_[hidden]> wrote:
>P3111 (https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2025/p3111r8.html), which has been adopted into the current draft standard, introduces atomic "modify-write" operations (`store_add()`, etc). They are like read-modify-write operations (`fetch_add()`, etc) except that they do not return the old value, and do not have the semantics of a read from the object.
>
>Most of the changes from P3111 make sense to me, but the one that doesn't is the change to [intro.progress] (https://eel.is/c++draft/intro.progress#1.5), which basically says that, unlike all other atomic operations, modify-write operations do not count as "forward progress". There doesn't seem to be any explanation or rationale given by P3111 for this part.

The design intent is captured in R6:
 https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2025/p3111r6.html#Forward-progress

"SG1 design intent is that Reduction operations are not a step, infinite loops around reductions operations are not UB (practically implementations can assume reductions are finite), but such loops may cause all threads to loose forward progress."

It is unclear to me how the wording achieves the stated intent.

>
>This seems strange because it's quite normal to have an infinite loop which shares its results with other threads via atomic operations, and modify-write operations might otherwise be suitable for that purpose. For instance:
>
>std::atomic<unsigned long> current_best{0};
>
>void try_to_improve() {
> while (true) {
> unsigned long result = compute(); // assume this has no forward progress steps
> current_best.store_max(result);
> }
>}
>
>The idea would be for other threads to be able to get the best (i.e. greatest) known value at any time with an atomic load from `current_best`.
>
>Under the current draft standard including P3111, this code has undefined behavior, because the loop does not make forward progress. But if we replaced `store_max()` with `fetch_max()`, even without looking at the returned value, it would be legal and behave as expected. It would also be legal if we kept a local record of our best result and periodically did a plain store to `best_found_so_far` (assuming here that we are the only thread doing this task).
>
>So I don't really understand why modify-write operations are excluded here, since loops including them can certainly be making forward progress in the commonly understood sense. It's also strange since the standard is otherwise so conservative in counting *all* other atomic operations as progress, including operations that do not achieve any real progress, or have no observable effect. For instance, an atomic load whose value is discarded is still forward progress for the purposes of [intro.progress]. So, for that matter, is `std::atomic_thread_fence(std::memory_order_relaxed)`, which is *explicitly* defined as "has no effects" (https://eel.is/c++draft/atomics.fences#5.1). (Though Clang doesn't respect this properly: https://github.com/llvm/llvm-project/issues/62057, https://github.com/llvm/llvm-project/issues/96702).
>
>Is there something I'm missing that would make this seem more sensible?

Received on 2025-09-28 06:55:56