6

Is it okay to use std::array<std::byte, N> storage as storage for allocation of some memory? For example, is it safe to call placement-new on a.data(), even though the lifetime of the allocated object has nothing to do with std::array?

std::aligned_storage_t was deprecated in C++23 for a reason, that it has UB rooted in its design (and poor API but it's not the case in this question), while it's pretty much close to this use case of std::array, so is it mandatory to use C-style array std::byte storage[N] as a storage for memory allocation, not std::array (or maybe even std::vector).

6
  • 2
    related stackoverflow.com/questions/71828288/… or answers the question, not sure. Your std::array is not aligned. Commented Feb 12, 2024 at 16:29
  • @KamilCuk assuming, that function, that calls placement new, takes care of alignment by adding up to address divisble by alignment of T. for example: std::array<std::byte, 2 * sizeof(std::uint32_t) - 1> arr; std::uint32_t* do_allocate() { new(arr.data() + (sizeof(std::uint32_t) - static_cast<std::uintptr_t>(arr.data()) % sizeof(std::uint32_t)) % sizeof(std::uint32_t)) std::uint32_t(1000); }, is it UB? Commented Feb 12, 2024 at 16:40
  • I see nothing in the linked paper that applies to std::array. If you do, please quote the specific passages you think are relevant. Commented Feb 12, 2024 at 16:41
  • 1
    @n.m.couldbeanAI it addresses "fails to provide storage" as one of the reasons of UB related to something-something with lifetimes. if this problem is present in std::aligned_storage_t, then it's probably present in std::array Commented Feb 12, 2024 at 16:42
  • 2
    An array (C-style) can provide storage. aligned_* does not give access to any array. std_array does, via its data(). So this point is not valid. Commented Feb 12, 2024 at 16:51

1 Answer 1

4

The issue with std::aligned_storage_t is that when you start the lifetime of the new object, the lifetime of the std::aligned_storage_t object ends. [basic.life]p1:

The lifetime of an object o of type T ends when:

  • [...]
  • the storage which he object occupies [...] is reused by an object that is not nested within o.

So if you were to use the std::array as if it were std::aligned_storage_t, you would not be able to call any of the member functions of the array since the arrays lifetime would end:

alignas(T) std::array<std::byte, sizeof(T)> arr;
T* ptr = std::construct_at(reinterpret_cast<T*>(&arr), ...);
// Lifetime of `arr` ends
// arr.data();  // UB: lifetime of `std::array` has ended

However, since you use the .data() pointer of std::array, then the lifetime would not end because the member std::byte[N] would provide storage for the new object [intro.object]p3:

If a complete object is created in storage associated with another object e of type “array of N unsigned char” or of type “array of N std​::​byte” that array provides storage for the created object if [...]

And by [intro.object]p4:

An object a is nested within another object b if:

  • a is a subobject of b, or
  • b provides storage for a, or
  • the exists an object c where a is nested within c, and c is nested within b.

So instantiate the second option with b = the member std::byte[N] of arr and c = the newly constructed object, and the third option with c = the member std::byte[N] and a = the newly constructed object and b = the std::array<std::byte, N> object. The newly constructed object is nested within the std::array.

So it can be used in the same way as if the object were std::byte[N] instead of std::array<std::byte, N> (namely to provide storage for a new object that is nested in the array/std::array):

alignas(T) std::array<std::byte, sizeof(T)> arr;
T* ptr = std::construct_at(reinterpret_cast<T*>(arr.data()), ...);
// Lifetime of `arr` continues: The array provides storage for the `T` object.
for (std::byte repr : arr) {
    // bytes of the object representation of `*ptr`
}
T* ptr2 = std::launder(reinterpret_cast<T*>(arr.data());  // Points to the same object as `*ptr`
Sign up to request clarification or add additional context in comments.

11 Comments

All the same things are true about C-style arrays.
@n.m.couldbeanAI No, because the T object would be nested within the array of std::byte: timsong-cpp.github.io/cppwp/n4868/intro.object#4.2
The underlyinhg array of std::array provides storage and the T object is nested within it.
@MooingDuck Can be nicely achieved with a union: godbolt.org/z/5Txzn5q54
@LanguageLawyer storage being "associated" doesn't seem to have a formal definition, but to be nested within the storage has to be "associated" with an array of unsigned char/std::byte, and that would presumably require a pointer to a byte array or pointer to a byte inside a byte array, not a pointer to a thing that contains a byte array (consider if there were other members out of the byte array, surely the intent would be that struct X { alignas(int) char x[sizeof(int)]; int y; } o; std::construct_at(reinterpret_cast<int*>(&o), 0); ends the lifetime of x, where &o.x wouldn't)
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.