When an array of objects of type T
(without constraints on T
) has been created (implicitly or explicitly) inside a storage, whose memory location is given, for instance, by a void *
, unsigned char *
or std::byte *
pointer (let say storage
), how can a pointer to the original array be retrieved from storage
.
When using std::launder
to retrieve a T *
pointer arr
, it seems that we get only a pointer to a single element: for instance, pointer arithmetic cannot be used anymore (in theory, because, practically it seems that at least mainstream compilers do what is expected).
A typical use-case would be some application that stores a buffer where objects are created. Then functions are called on this storage. They retrieve the actual objects and process them:
#include <cstddef>
#include <new>
#include <span>
template <typename T>
void process(void* storage, std::size_t n) {
T* arr = std::launder(reinterpret_cast<T*>(storage)); // (4)
// is arr the address of a single object or the address of the existing
// array
// (see usage in main)
// (5) can I use arr as an array (arr[0] ... arr[count-1] being valid, arr+n
// being valid (for 0<=n<=count) and arr+count being the valid pointer to
// one past the last element)?
for (auto& el : std::span(arr, arr + n)) {
el = T{2};
}
}
int main(int argc, char**) {
std::size_t count = static_cast<std::size_t>(10 * argc);
constexpr static std::size_t sz_in_bytes{1000};
// provides storage
alignas(double) std::byte storage[sz_in_bytes]; // (1)
// place an array of double inside storage
// NB: DR2382 guarantees that there is no offset between darr and storage
auto* darr = ::new (static_cast<void*>(storage)) double[count]; // (2)
for (auto& el : std::span(darr, darr + count)) {
el = double{1};
}
// passing the array as its storage
process<double>(static_cast<void*>(storage), count); // (3)
return static_cast<int>(darr[3]);
}
double
here);process
as the pointer to it's storage (it might correspond to some erasure type scheme);double
to process it.span
,...In this example, AFAIU, arr
inside process
is the address of a single object, not this of the array darr, thus the loop is UB.
How can we retrieve an array of runtime-known size? Is it possible with std::launder
?
std::start_lifetime_as_array
might be an option but it won't work with some other types (the array should be trivially copiable).
Afterthought: from Emulate std::start_lifetime_as_array in C++20 and relaxing the constraint on trivially copiable:
T* arr = std::launder(reinterpret_cast<T*>(std::memmove(storage,storage,sizeof(T)*count));
This implicitly create an array of T
(arrays are always implicit lifetime types). And the value representation is kept from the existing bytes? But under the same line of thought why std::launder
here would retrieve an array instead of a single element?
Placement new returns a pointer to the object or array created. std::launder
is used when you don't have a pointer to the object you want to access, but instead have some other pointer that represents the same address. The pointer returned from launder
, if certain conditions are met, will give you a pointer to the object of the specified type that lives at the specified address.
Ergo, to access the array of double
s through darr
, you don't need std::launder
because darr
already points to the first element of that array. To access the array of double
s through storage
, you would need std::launder
, because storage
refers to the std::byte
array and not the double
array you created within it, nor any element of that array.
If you've "lost" the pointer value darr
and need to "reconstruct" it using std::launder
you can do so like this:
auto* darr = std::launder(reinterpret_cast<double*>(&storage));
This gives you a pointer to the first element of the array of double
s, which is the same as what the placement new expression gave you originally.