To get around aliasing and byte reinterpretation rules, I have a utility function called T* landry_pod<T>(void*)
that pretends to copy bytes around and create new objects. It compiles down to nothing under optimization, as every compiler can see I put the bytes back where they started.
template<class T>
T* laundry_pod( void* data ){
static_assert( std::is_pod<T>{} ); // or more verbose replacement as pod is gone
char tmp[sizeof(T)];
std::memcpy( tmp, data, sizeof(T) );
T* r = ::new(data) T;
std::memcpy( data, tmp, sizeof(T) );
return r;
}
This ensures the sizeof(T)
bits data points at are identical, but returns a pointer to an object of type T
there. It is a standards compliant way to do T* r = (T*)data;
when data
just points at bits but not an actual object. It optimizes down to 0 instructions at runtime.
Sadly, while it does nothing at runtime, it logically cannot be used on a const
buffer.
This is my attempt to modify it to work with const
input and output:
template<class T, std::enable_if_t<std::is_const<T>{}, bool> = true, class In>
T* laundry_pod( const In* data ){
static_assert( sizeof(In)==1 ); // really, In should be byte or char or similar
static_assert( std::is_pod<T>{} ); // or more verbose replacement as pod is gone
std::byte tmp[sizeof(T)];
std::memcpy( tmp, data, sizeof(T) ); // copy bytes out
for(std::size_t i =0; i<sizeof(T); ++i)
data[i].~In(); // destroy const objects there // is this redundant?
auto* r = ::new( (void*)data ) std::remove_const_t<T>; // cast away const on data (!)
std::memcpy( r, tmp, sizeof(T) ); // copy same bytes back
return r;
}
here I destroy the const objects (well bytes) then construct a new object in their place.
The above should optimize to 0 instructions (try it), but I had to cast away const when I created r
.
If data
is pointing to a contiguous buffer of const char or byte, is destroying those objects sufficient to permit me to reuse the storage and remain in defined behaviour land? Or would simply creating new objects be sufficient? Or am I doomed?
Assume noone uses the old In
pointer to access the original bytes before hand.
The core of your question is it defined behavior to reuse the storage of a const
object? The answer is no, according to basic.life#9:
Creating a new object at the storage location that a const object with static, thread, or automatic storage duration occupies or, at the storage location that such a const object used to occupy before its lifetime ended results in undefined behavior.
[ Example:
struct B { B(); ~B(); }; const B b; void h() { b.~B(); new (const_cast<B*>(&b)) const B; // undefined behavior }
— end example ]
Ostensibly this is because the data may be placed in read-only memory. So it is not surprising that trying to modify const
data is a no-op.