c++language-lawyerc++20undefined-behaviorlifetime

std::construct_at and object lifetime?


I was reviewing a code I proposed to initialize a std::array at compile-time for non-default-constructible objects: https://stackoverflow.com/a/78676552/21691539

in the second version I'm doing:

// creating storage on std::byte to benefit from "implicit object creation"
alignas(alignof(T)) std::byte storage[sizeof(std::array<T, N>)];
std::array<T, N>* const Array =
    std::launder(reinterpret_cast<std::array<T, N>*>(storage));

// initializing objects in the storage
T* it =
    Array->data();  // construct objects in storage through std::construct_at
for (std::size_t i = 0; i != N; ++i) {
    std::construct_at(it, gen(static_cast<int>(i))); // UB?
    // new (it) T(gen(static_cast<int>(i))); // not UB?
    ++it;
}

LIVE

I wanted to rely on the fact that std::array is an implicit lifetime type but I'm thinking now that I'm UB for the following reason: Array has indeed started its lifetime but none of his subobjects of type T:

Some operations are described as implicitly creating objects within a specified region of storage.[...] [ Note: Such operations do not start the lifetimes of subobjects of such objects that are not themselves of implicit-lifetime types. — end note ]

https://timsong-cpp.github.io/cppwp/n4861/intro.object#10

Does std::construct_at starts this lifetime or just calls the constructor of an object that is already alived inplace (in which case my code would be UB)?

What made me have doubts is the example provided in cppreference that goes through a std::bit_cast to start an object lifetime (thus, AFAIU, in a different memory location than the provided storage).


further research: std::construct_at:

Effects: Equivalent to: return ::new (voidify(*location)) T(std::forward(args)...);

https://timsong-cpp.github.io/cppwp/n4861/specialized.construct#2

Objects creation 'emphasis mine':

An object is created by a definition, by a new-expression, by an operation that implicitly creates objects (see below), when implicitly changing the active member of a union, or when a temporary object is created.

https://timsong-cpp.github.io/cppwp/n4861/intro.object#1

Linking these two section, is it correct to say that std::construct_at, in fact, actually start an object lifetime at the given location?


Solution

  • According to [basic.life]/1, an object's lifetime starts immediately after its initialization is complete, including "vacuous initialization", which is the case where the initialization phase does nothing at all (such as default-initialization for an object of scalar type).

    std::construct_at is specified to use the new operator, and new is specified to initialize the object created ([expr.new]/24), so the lifetime begins. Note that the new operator, similarly to an object definition, performs "ordinary" object creation that, unlike implicit object creation, is guaranteed to recursively initialize bases and members according to the usual rules (e.g., [class.base.init], [dcl.init.aggr]).