I was reviewing a code I proposed to initialize a std::array
at compile-time for non-default-constructible objects: https://mcmap.net/q/905632/-idiom-for-initializing-an-std-array-using-a-generator-function-taking-the-index
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;
}
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?
construct_at
, no UB is involved here. – Hannahhannanstd::construct_at
start object lifetime? – Spokeconstruct_at
? But using it requires manual destruction of the objects withdestruct_at
if their destructor is expected to have any effect (including mere destruction of subobjects). – Hannahhannanalignas(alignof(T))
can be shortened toalignas(T)
. – Sarnoffstd::array
object inside thestorage
before you can then safely access its members, likedata()
. The code shown is merely type-casting thestorage
without actually creating thestd::array
object.std::array
is itself a struct after all. It needs to be constructed and destroyed like any other class type. Especially ifT
is not a trivial type. – Glorifystd::array
covered by the rules outlined in en.cppreference.com/w/cpp/named_req/ImplicitLifetimeType ? Again, I go back to the issue withstd::array
holding a non-trivial type, does that allow or disallowstd::array
from being an implicit lifetime type? – Glorifystd::array<T,N>
is an implicit-lifetime type due to class.prop/9, independently of whatT
is. – Sarnoff