Search code examples
c++c++23

Roundtripping a std::list::iterator through void * with std::bit_cast


Assuming userdata is never dereferenced, why is the following undefined behaviour?

std::list::iterator iter = [ ... ];
auto userdata = std::bit_cast<void *>(iter);
c_api_set_void_ptr(userdata);
auto iter = std::bit_cast<std::list::iterator>(c_api_get_void_ptr(userdata));
auto elem = *iter;

bit_cast checks the sizes and triviality, leaving the possibility of producing an invalid value of one of the types. Are there restrictions on values of void *? Does the situation change when limited to x86-64?


Solution

  • Assuming libstdc++/libc++/MSVC STL:

    If any debug mode was enabled that made the list iterator hold more members, this would be ill-formed and fail to compile because the sizeof(iterator) would be too large.

    Otherwise, in all three of these implementations, there is simply a pointer to an internal list node. On x86-64, all pointers have the same value representation and size, so this bit cast would be valid and produce a valid pointer (to the list node). Since there are no padding bits on an x86-64 void* pointer, no bits should be changed when this void* pointer is copied all over the place, giving you a void* pointer with the same value, which when bit_cast back gives you an equivalent iterator.

    On a different standard library implementation, a std::list<T>::iterator might hold something besides a pointer that still has the same size. Or on a different platform the list node pointers might have a different layout than void* pointers. If the bit_cast does compile, an invalid pointer value might be produced.

    Copying an invalid pointer has implementation defined behaviour. On an x86-64, copies will be fine and preserve all the bits, and this program will still be well formed. On a platform with extra pointer safety instructions, this might mangle the pointer value (or segfault or something). This is still implementation defined behaviour.

    So your program is either ill-formed at compile time (because the bit_cast fails to compile) or implementation defined. There is no outright undefined behaviour.