// How can we make this program well-defined without sacrificing efficiency? If the destination type // is a trivially-copyable implicit-lifetime type, this can be accomplished by copying the storage // elsewhere, using placement new of an array of byte-like type, and copying the storage back to its // original location, then using std::launder to acquire a pointer to the newly-created object, and // finally relying on the compiler to optimise away all the copying. However, this would be very // verbose and hard to get right. For expressivity and optimisability, a combined operation to // create an object of implicit-lifetime type in-place while preserving the object representation // may be useful. This is exactly what std::start_lifetime_as is designed to do template T* start_lifetime_as(void* p) noexcept { // Copy the storage to a temporary location, using placement new of an array of byte-like type std::byte tmp[sizeof(T)]; new (tmp) T(*reinterpret_cast(p)); // Copy the storage back to its original location std::copy(tmp, tmp + sizeof(T), reinterpret_cast(p)); // then using std::launder to acquire a pointer to the newly-created object // and finally relying on the compiler to optimise away all the copying return std::launder(reinterpret_cast(p)); } // The same operation can be performed on an array of implicit-lifetime type template T* start_lifetime_as_array(void* p, size_t n) noexcept { // Copy the storage to a temporary location, using placement new of an array of byte-like type std::byte tmp[sizeof(T) * n]; for (size_t i = 0; i < n; i++) { new (tmp + i * sizeof(T)) T(reinterpret_cast(p)[i]); } // Copy the storage back to its original location std::copy(tmp, tmp + sizeof(T) * n, reinterpret_cast(p)); // then using std::launder to acquire a pointer to the newly-created object // and finally relying on the compiler to optimise away all the copying return std::launder(reinterpret_cast(p)); } template const T* start_lifetime_as(const void* p) noexcept { return start_lifetime_as(const_cast(p)); } template volatile T* start_lifetime_as(volatile void* p) noexcept { return start_lifetime_as(const_cast(p)); } template const volatile T* start_lifetime_as(const volatile void* p) noexcept { return start_lifetime_as(const_cast(p)); } template const T* start_lifetime_as_array(const void* p, size_t n) noexcept { return start_lifetime_as_array(const_cast(p), n); } template volatile T* start_lifetime_as_array(volatile void* p, size_t n) noexcept { return start_lifetime_as_array(const_cast(p), n); } template const volatile T* start_lifetime_as_array(const volatile void* p, size_t n) noexcept { return start_lifetime_as_array(const_cast(p), n); } // Test of the above functions int main() { // Create an implicit-lifetime object std::array record; std::fill(record.begin(), record.end(), std::byte(0x42)); struct Foo { int x; int y; }; // Create a new object of implicit-lifetime type in-place while preserving the object // representation Foo* foo = start_lifetime_as(record.data()); printf("%d %d", foo->x, foo->y); Foo* foos = start_lifetime_as_array(record.data(), record.size() / sizeof(Foo)); for (size_t i = 0; i < record.size() / sizeof(Foo); i++) { printf("%d %d", foos[i].x, foos[i].y); } // Test const version const std::array record2 = record; const Foo* foo2 = start_lifetime_as(record2.data()); printf("%d %d", foo2->x, foo2->y); }