std::construct_at to use memory from std::byte / unsigned char arrays

156 Views Asked by At

It is a very common example to use std::byte / unsigned char arrays with std::construct_at to provide the storage.

//say A is a NON standard-layout class user-defined class
alignas(A) std::byte storage[sizeof(A)];
std::construct_at(reintrepret_cast<A*>(storage), A{});

However, std::construct_at accepts a pointer to the object, while if you reintrepre_cast a pointer to std::byte / unsigned char array it should still point to the first member of std::byte / unsigned char array. So is the code above illegal? And if so, how to provide a valid pointer to std::construct_at?

3

There are 3 best solutions below

0
minex On

cppreference is not a standard so I don't see it as a reliable source. It has to be proven by a quote from the standard.

That storage points to the array of unsigned char in your example and not to A. Although I think the reason construct_at takes a pointer, is because it wants to know the type of the object it wants to construct, and the pointer to A is converted to void anyway by the implementation of construct_at. This way it is still compliant with the standard.

Note here it is said that storage is an array object!

If a complete object is created ([expr.new]) in storage associated with another object e of type “array of N unsigned char” or of type “array of N std​::​byte” ([cstddef.syn]), that array provides storage for the created object if: (3.1) the lifetime of e has begun and not ended, and (3.2) the storage for the new object fits entirely within e, and (3.3) there is no array object that satisfies these constraints nested within e.

Here it says what can be done with reintepret_cast and obviously unsigned char array and A are not pointer-interconvertible.

If two objects are pointer-interconvertible, then they have the same address, and it is possible to obtain a pointer to one from a pointer to the other via a reinterpret_cast ([expr.reinterpret.cast]). [Note 4: An array object and its first element are not pointer-interconvertible, even though they have the same address. — end note]

However, reintepret_cast still can be called as long as it is result is not used apart from converting it to another type

An object pointer can be explicitly converted to an object pointer of a different type.57 When a prvalue v of object pointer type is converted to the object pointer type “pointer to cv T”, the result is static_cast<cv T*>(static_cast<cv void*>(v)).

1
Nicol Bolas On

However, std::construct_at accepts a pointer to the object, while if you reintrepre_cast a pointer to std::byte / unsigned char array it should still point to the first member of std::byte / unsigned char array.

And?

construct_at does not treat the pointer as if it pointed to an object within its lifetime. It does not attempt any operation on this pointer which would result in a violation of [basic.lval]/11. That is, it doesn't attempt to access a glvalue of type T using that pointer. Indeed, all the C++20 standard says about its behavior with regard to the pointer is this:

Equivalent to:

 return ::new (voidify(*location)) T(std::forward<Args>(args)...);

Where voidify is just a complicated cast to void*.

0
Jan Schultke On

You seem to be worried that converting between pointers for use in std::construct_at is somehow problematic because you don't have an A object, and perhaps strict aliasing is violated in some way.

Those worries are unnecessary. The code:

std::construct_at(reinterpret_cast<A*>(storage), A{});

... is equivalent to ([expr.reinterpret.cast] p7):

std::construct_at(static_cast<A*>(static_cast<void*>(storage)), A{});

... which is equivalent to ([specialized.construct] p2):

// note: not an exact equivalence, because the std::construct_at version created
//       an additional temporary A{} object
::new (static_cast<void*>(static_cast<A*>(static_cast<void*>(storage)))) A{};

... which is equivalent to ([expr.static.cast] p14):

// note: both the conversion to and from void* leave the pointer value unchanged,
//       so they can be simplified away
// note: an array and its first element have the same address,
//       so we can use both storage and &storage
::new (static_cast<void*>(storage)) A{};

A conversion to void* doesn't require an actual object to exist. There also can't be a strict aliasing violation because you are beginning the lifetime of a new object, not accessing an existing one. Your code is valid.

Other answers in this Q&A mention pointer-interconvertibility, which is not relevant. What matters is the conversion to/from void*, which is a conversion that leaves the pointer value (and thus the address that the pointer represents) unchanged ([conv.ptr] p2). Pointer-interconvertibility would only be relevant to aliasing, and none of this code is performing aliasing.