c++arrayslanguage-lawyerstdlaunder

retrieving a runtime array from a pointer to its storage?


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]);
}

LIVE

  1. I have some storage;
  2. I place an array in it (of double here);
  3. I pass the array to process as the pointer to it's storage (it might correspond to some erasure type scheme);
  4. I want to get back a pointer to the array of double to process it.
  5. From this pointer I want to perform pointer arithmetic, array element access, creating a 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?


Solution

  • 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 doubles through darr, you don't need std::launder because darr already points to the first element of that array. To access the array of doubles 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 doubles, which is the same as what the placement new expression gave you originally.