> If you do the loads as uint32, you lose the single atomic operations on two different values which was the whole point of this exercise.
There's no need for any flirting with undefined behaviour through type-punning.
When doing the atomic write, you prepare the uint64_t value to write by using bitwise operations, and then perform the atomic write of the resultant uint64_t value.
When doing the atomic read, you atomically read the uint64_t value, then use bitwise operations to unpack the original pair of uint32_t values.
Put differently, writing is done by pack-then-atomically-write, and reading is done by atomically-read-then-unpack.
Turns out we're both overthinking it though, there's a more direct way: use a struct containing an array of 2 uint32_t elements, or declare a struct with 2 uint32_t members. Both C and C++ support atomic reads and writes of user-defined types. For a C++ example showing this see [0]. This will be atomic and, presumably, should be lock-free where possible (hard to imagine the compiler would introduce padding in the struct type that would sabotage this).
> Using a single uint64 as the memory type works, but you no longer have two different names fields and have to pack/unpack them by hand.
Yes, the stored variable would hold 2 different meaningful values, which is a little ugly.
> There's no ub if you use the compiler extension, just totally clear code that does the right thing
Anyone with a deep knowledge of the language will quickly recognise it as incorrect per the language standard. I wouldn't call that totally clear code that does the right thing.
Your proposed solution is only assured to behave as expected if the correct compiler-specific flags are used, otherwise it will introduce undefined behaviour. There's no guarantee that a compiler will even offer such a flag. It's also likely to trigger compiler warnings.
Using a single uint64 as the memory type works, but you no longer have two different names fields and have to pack/unpack them by hand.
There's no ub if you use the compiler extension, just totally clear code that does the right thing