Created
December 9, 2022 19:00
-
-
Save GavinRay97/b69cb6ebab6a0e13d2cfe74a6ed7cdc6 to your computer and use it in GitHub Desktop.
P2590R2: "Explicit lifetime management" (start_lifetime_as) implementation
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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<class T> | |
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<T*>(p)); | |
// Copy the storage back to its original location | |
std::copy(tmp, tmp + sizeof(T), reinterpret_cast<std::byte*>(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<T*>(p)); | |
} | |
// The same operation can be performed on an array of implicit-lifetime type | |
template<class T> | |
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<T*>(p)[i]); | |
} | |
// Copy the storage back to its original location | |
std::copy(tmp, tmp + sizeof(T) * n, reinterpret_cast<std::byte*>(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<T*>(p)); | |
} | |
template<class T> | |
const T* | |
start_lifetime_as(const void* p) noexcept | |
{ | |
return start_lifetime_as<T>(const_cast<void*>(p)); | |
} | |
template<class T> | |
volatile T* | |
start_lifetime_as(volatile void* p) noexcept | |
{ | |
return start_lifetime_as<T>(const_cast<void*>(p)); | |
} | |
template<class T> | |
const volatile T* | |
start_lifetime_as(const volatile void* p) noexcept | |
{ | |
return start_lifetime_as<T>(const_cast<void*>(p)); | |
} | |
template<class T> | |
const T* | |
start_lifetime_as_array(const void* p, size_t n) noexcept | |
{ | |
return start_lifetime_as_array<T>(const_cast<void*>(p), n); | |
} | |
template<class T> | |
volatile T* | |
start_lifetime_as_array(volatile void* p, size_t n) noexcept | |
{ | |
return start_lifetime_as_array<T>(const_cast<void*>(p), n); | |
} | |
template<class T> | |
const volatile T* | |
start_lifetime_as_array(const volatile void* p, size_t n) noexcept | |
{ | |
return start_lifetime_as_array<T>(const_cast<void*>(p), n); | |
} | |
// Test of the above functions | |
int | |
main() | |
{ | |
// Create an implicit-lifetime object | |
std::array<std::byte, 100> 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<Foo>(record.data()); | |
printf("%d %d", foo->x, foo->y); | |
Foo* foos = start_lifetime_as_array<Foo>(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<std::byte, 100> record2 = record; | |
const Foo* foo2 = start_lifetime_as<Foo>(record2.data()); | |
printf("%d %d", foo2->x, foo2->y); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment