I'm working on a cross-platform data structure and trying to define a compact union-based layout that allows atomic access to a 64-bit word, while also optionally accessing the lower 32-bit fields.
I want to support:
- MSVC x64 and ARM64
- Clang x64 and ARM64
- On Windows, macOS, and Linux
What I want:
I would like to define a structure like this:
struct tTWUnion { union { std::atomic<uint64_t> uValue; struct { std::atomic<uint32_t> uID; std::atomic<uint32_t> uSwitchPointID; }; }; }; Then use it like:
tTWUnion tun; tun.uValue.store(100); // or tun.uID.store(42); tun.uSwitchPointID.store(58); But this fails to compile because std::atomic is non-trivial, and placing multiple std::atomic inside a union causes the compiler to delete the default constructor (which I want to avoid manually overriding for portability reasons).
If I instead do:
struct tTWUnion { union { std::atomic<uint64_t> uValue; struct { uint32_t uID; uint32_t uSwitchPointID; }; }; }; Then I can compile it, but now only uValue is atomic — and accessing uID or uSwitchPointID breaks atomicity guarantees, and could lead to tearing or data races.
What I don't want:
- I don’t want to use placement new and manual destructors (which makes code harder to maintain across platforms).
- I don’t want to rely on undefined behavior or non-portable hacks, but I also want to avoid duplicating data if possible.
My question:
Is there a standard and portable way to define such a union layout where both full 64-bit atomic access and partial 32-bit atomic fields can coexist?
Why exactly does placing multiple std::atomic inside a union lead to constructor issues?
Are there any known methods for MSVC/Clang that can allow this safely?
Edit:
As an alternative idea, I’m wondering if something like the following structure could work safely and portably:
struct tTWUnion { union { uint64_t uValue; struct { uint32_t uID; uint32_t uSwitchPointID; }; }; }; And then use it like:
std::atomic<tTWUnion> atomicUnion; tTWUnion value = {}; value.uValue = 100; atomicUnion.store(value); tTWUnion value2 = {}; value2.uID = 42; value2.uSwitchPointID = 99; atomicUnion.store(value2); Is using std::atomic like this valid if sizeof(tTWUnion) == 8?
.storebeing read by a 32-bit atomic.loadof one half? (That's is fine in asm, but C++ doesn't have a way to express it without technically being UB). Or is this just for reuse of space in different phases of your program?uint64_tand accessing its "parts" using bit masks?