From C++ standard:
Attempting to read or modify the stored value through any other type results in undefined behavior.
The first basic example illustrating this principle:
#include <cstdint>
std::uint32_t toInt(float value)
{
auto* p = reinterpret_cast<std::uint32_t*>(&value); // The cast itself is permitted.
return *p; // Undefined behavior: accesses a float object through a std::uint32_t glvalue.
}
An example demonstrating what we do in develop branch in our project:
#include <cstddef>
#include <cstdint>
struct Packet
{
std::int8_t x;
std::int16_t y;
std::int32_t z;
};
struct A
{
float value;
};
static_assert(sizeof(A) < sizeof(Packet));
static_assert(alignof(A) <= alignof(Packet));
A getA(const Packet* packet)
{
const auto* bytes = reinterpret_cast<const std::byte*>(packet);
// Allowed: a Packet's object representation may be inspected through std::byte.
const auto* a = reinterpret_cast<const A*>(bytes);
// The cast itself is allowed, but it does not create an A object.
return *a;
// Undefined behavior: no A object exists at this address.
// Casting through std::byte does not start the lifetime of an A object.
}
An example demonstrating how we tried to fix this:
#include <cstddef>
#include <cstdint>
#include <new>
struct Packet
{
std::int8_t x;
std::int16_t y;
std::int32_t z;
};
struct A
{
float value;
};
static_assert(sizeof(A) < sizeof(Packet));
static_assert(alignof(A) <= alignof(Packet));
A toA(const Packet* packet)
{
auto* mutablePacket = const_cast<Packet*>(packet);
// The const_cast itself is allowed because the original object in main
// is not actually const.
auto* bytes = reinterpret_cast<std::byte*>(mutablePacket);
// This conversion only produces a pointer to the Packet's storage.
auto* a = ::new (bytes) A;
// Placement new creates an A object and starts its lifetime.
// Reusing the storage ends the lifetime of the entire Packet object.
// A is default-initialized, but its trivial default constructor does
// not initialize A::value, so value remains indeterminate.
return *a;
// Undefined behavior: copying *a reads the indeterminate float value.
}
int main()
{
Packet packet{1, 2, 3}; // The original object is not const.
const A a = toA(&packet);
// Undefined behavior occurs inside toA when the indeterminate
// A::value member is read while constructing the returned A object.
// Accessing packet here would also be undefined behavior because
// its lifetime ended when the A object was created in its storage.
return 0;
}
So basically we did this:
int main()
{
int* value = new int; // Default-initialized; the value is indeterminate.
return *value; // Undefined behavior: reads an indeterminate int.
}
From C++ 23 standard:
If an indeterminate value is produced by an evaluation, the behavior is undefined …


An example of placement new usage in a similar scenario: https://stackoverflow.com/questions/79998366/using-stdstart-lifetime-as-multiple-times-with-the-same-source-object
It does std::memcpy after placement new.