First, for your second line
Node* dummy = reinterpret_cast<Node*>(new int8_t[sizeof(Node)]);
by itself.
new
returns a pointer to the first int8_t
object in the array of int8_t
objects it created.
reinterpret_cast
's behavior depends on the alignment of the address represented by the pointer. If it is suitably aligned for an object of type Node
, then it will leave the pointer value unchanged (since there is definitively no Node
object at the location which is pointer-interconvertible with the int8_t
object). If it is not suitably aligned, the returned pointer value will be unspecified.
Unspecified means that we won't know what the value will be, but it wont cause undefined behavior.
Therefore, in any case, the second line and the cast by itself do not have undefined behavior.
The line
dummy->prev = ... /* last node */;
requires that the object dummy
points to is actually a Node
object. Otherwise it has undefined behavior. As mentioned above, reinterpret_cast
gives us either an unspecified value or a pointer to the int8_t
object. This already is an issue, that I think at least requires a std::launder
call.
Even if the pointer returned from new
is correctly aligned, then we still need to check whether a Node
object is present. We certainly did not create any such object in any of the shown operations explicitly, but there is implicit object creation which may help out (at least since C++20, but I suppose this was supposed to be a defect report against older standard versions).
Specifically, objects may be created implicitly inside an array of types unsigned char
, std::byte
and, with some limitations, char
(CWG 2489) when the lifetime of the array is started. int8_t
is usually signed char
and I think is not allowed to be either of the three previously mentioned types (see e.g. this question). This removes the only possible way out of UB.
So your third code line does have undefined behavior.
Even if you remedy this by changing the type form int8_t
to std::byte
, there are other constraints on the details of Node
to make the implicit object creation possible. It may also be necessary to add a std::launder
call.
All of this doesn't consider the alignment yet, because although new[]
obtains memory with some alignment requirements, I think the standard mandates new[]
itself to return a pointer with stronger alignment than required for the element type only for char
, unsigned char
and std::byte
array new
.
Many of these issues can probably be avoided by using e.g. operator new
directly, possibly with provided alignment request, and making sure that Node
is an aggregate.
In any case writing code like this is very risky because it is difficult to be sure that it isn't UB. It should be avoided when ever possible.