By the way, I think we should go beyond C++ and offer a primitive that allows the ignored-bits mask to be dynamic. We should also say that, even if you don’t ignore a bit which turns out to be uninit in memory, invoking the compare-exchange primitive isn’t UB. The uninit bits just potentially (nondeterministically) cause the compare-exchange to fail. This guarantee can be limited to types that actually allow uninit bits in the positions that turn out to be uninit.
Why is the above useful? Because it would make it possible to soundly compare-exchange enum values, even if they have variant-dependent padding bits. A compare-exchange on an enum value would lower to a masked compare-exchange where the ignored bits are the padding bits of the expected variant. If the actual value in memory is also that variant, then the padding bits are ignored and everything works as expected. But if the actual value is a different variant, then it may have uninit bits in non-ignored positions. So for this approach to be sound, the compare-exchange can’t be automatic UB just because of the uninit bits. But it doesn’t matter whether those bits are considered matching or not; the compare-exchange should fail regardless due to the non-matching discriminant (whose bits wouldn’t be ignored).