It's implementation-defined, and still valid in C++17, at least for GCC. You cannot perform an xor operation between two pointers directly; you would have to go through reinterpret_cast<std::intptr_t>
. The effect of this conversion (and back) is implementation-defined.
Implementation-defined means that the compiler must document what happens. What GCC provides is:
A cast from pointer to integer discards [...], otherwise the bits are unchanged.
A cast from integer to pointer discards [...], otherwise the bits are unchanged.
When casting from pointer to integer and back again, the resulting pointer must reference the same object as the original pointer, otherwise the behavior is undefined.
See https://gcc.gnu.org/onlinedocs/gcc/Arrays-and-pointers-implementation.html
From this description, we can conclude that:
- the user ensures that the object at the address stays the same between storing pointers in an XOR list and retrieving them
- if the pointed-to object changes during this process, casting the integer back to a pointer is undefined behavior
- converting one past the end pointers, null pointers, and invalid pointers back and forth isn't explained here, but "preserves the value" according to the C++ standard
Implications for an XOR-list
Generally, this should make XOR-lists implementable, as long as we reproduce the same pointers that we stored, and don't "rug pull" nodes while there are XORed pointers to them.
std::intptr_t ptr;
// STORE:
// - bit-cast both operands and XOR them
// - store result in ptr
ptr = reinterpret_cast<std::intptr_t>(prev) ^ reinterpret_cast<std::intptr_t>(next);
// LOAD:
// - XOR stored ptr and bit-cast to node*
node* next = reinterpret_cast<node*>(ptr ^ reinterpret_cast<std::intptr_t>(prev));
// valid dereference, because at the address 'next', we still store the same object
*next;
As stated in the documentation, next
"must reference the same object as the original pointer", so we can assume that next
is now a pointer to an object, if such a pointer was originally stored in ptr
.
However, it would be UB if we stored the XORed next
pointer, began the lifetime of a new object where next
points to, and then un-XORed the address and converted back to a pointer type.