On Feb 16, 2025, at 06:40, Jennifier Burnett via Std-Discussion <std-discussion@lists.isocpp.org> wrote:

Ah, ok to follow on from this the main thing that seems to have passed me by is this in Nate's response:

>I presume that in the vast majority of cases, this happens-before is achieved simply by sequenced-before; within a single thread, with respect to program order, you access the object through a shared_ptr, then destroy that shared_ptr, and don't access the object again after that.

How is sequenced-before equivalent to happens-before without any provisions of ordering?

Sequenced-before implies happens-before.  It's right there in the definition: https://eel.is/c++draft/intro.races#10.1 

My understanding is that given the following:

std::atomic<int>* foo = ...
int* bar = ..

int temp = *bar;
foo->fetch_sub(1, std::memory_order_relaxed);

There is nothing that requires the read from *bar to happen before the fetch_sub, because they're from two independent variables and the atomic operation provides no ordering constraints.

It is definitely true that the read from *bar happens-before the fetch_sub (see above).  But it's also true that the read of *bar can be reordered with the fetch_sub.

It's tempting to think that "X happens-before Y" should mean something like "X must be observed before Y", or "X cannot be reordered with Y".  Neither of those is true in general.

The way I think about it is that "X happens-before Y" is only *directly* observable information when X and Y are two operations on the *same* object.  In that case, it means that Y observes X: following https://eel.is/c++draft/intro.races#13 for non-atomic objects, and p14-18 for atomic objects.

Having "X happens-before Y" for operations on two different objects isn't something you can directly observe; you can only use that information together with transitivity to indirectly learn something about a happens-before relationship between two operations on some other single object.  And it so happens that those semantics are satisfied by applying the usual reordering rules: anything can reorder with a relaxed operation, no reordering after a release or before an acquire.

However what you seem to be claiming (unless I'm misunderstanding, which I probably am) is that in the case of shared_ptr the fact that the read is sequenced-before the fetch_sub would be sufficient to prevent both the compiler and hardware from moving the read until after the fetch_sub. The thing I'm missing is exactly how that conclusion is being reached.

Right, sequenced-before alone does not prevent reordering.  But sequenced-before together with a release barrier does.  And that's the only way you can get a happens-before relationship between operations on different threads.  

So the proposed wording, the promised (strongly) happens-before between each shared_ptr destructor and the deleter, which are potentially in different threads, can in practice only be achieved by having a release barrier on the reference counter decrement.  And once that exists, then having your operation sequenced-before the shared_ptr destructor suffices.