c++arraysmemory-managementlanguage-lawyerpointer-arithmetic

Correct way to get an array through low-level allocation functions


Most low-level allocation functions return void *.

If I want to perform pointer arithmetic on the obtained memory and/or allocate an array, is this the correct way to do it without invoking undefined behavior, given that T is an implicit-lifetime type?

template <typename T>
auto* make_array(std::size_t N) {
    return std::launder(reinterpret_cast<T*>(
        ::operator new(N * sizeof(T), std::align_val_t{alignof(T)})));
}

LIVE


Additional background: in many code samples, I don't see the std::launder but I believe it is necessary to access the implicitly created array. Besides, there is currently a proposal P3006 that seems to make std::launder unecessary in this specific kind of construction, implying that it is required so far.

A similar question has already been asked but the accepted answer does not convince me, given this additional background.


Solution

  • Just to summarize the discussions in comment of the question (special thanks to @weijun-zhou), though the proposed snippet is not incorrect, it can be simplified to:

    template <typename T>
    auto* make_array(std::size_t N) {
        return static_cast<T*>(::operator new(N * sizeof(T), std::align_val_t{alignof(T)}));
    }
    

    The reasoning is as follows:

    1. ::operator new implicitly creates implicit-lifetime objects such as arrays of T, T being an implicit-lifetime type, and produces a safely-derived pointer value (2.1) to this array
    2. it returns it as a void* through a valid pointer conversion which is also a safely-derived pointer value (2.4)
    3. this can be cast back to the original T* through the same rule, ever through reinterpret_cast or through static_cast.

    The former being equivalent to static_cast<T*>(static_cast<void*>), using static_cast is more straightforward and its intent clearer.

    I'm missing a small piece of rule (though it's obvious) about the fact that not only the cast is legal but the values pointed to can be safely accessed.

    Eventually, the same should apply to C-style allocation functions (std::malloc,std::calloc,std::realloc), extra-care being required regarding alignment.