If I have an aggregate of integral types and I want to create a instance of it with random, is reinterpret_cast
being used here safe?
template <typename T>
auto random() -> T {
static auto random_device = std::random_device{};
static auto generator = std::mt19937(random_device());
static auto distribution = std::uniform_int_distribution<std::uint8_t>{};
auto bytes = std::array<std::uint8_t, sizeof(T)>{};
for (auto i = std::size_t{0}; i < bytes.size(); ++i) {
bytes[i] = distribution(generator);
}
return *reinterpret_cast<T *>(bytes.data());
}
Firstly this will not compile in the general case. You cannot
reinterpret_castinto an arbitrary type. If
T` is not a pointer type or an integral type this will not compile.
To fix this you probably want to return
return *reinterpret_cast<T*>(bytes.data());
But do not do that either. If T
is not ((un)signed) char
or std::byte
this is undefined behavior.
The correct way to implement such a function is to first make sure the target type T
satisfies the criteria to do this. I will over-restrict it by requiring that it is trivial:
decltype(auto) get_mt19937() {
static auto random_device = std::random_device{};
static auto generator = std::mt19937(random_device());
return generator;
}
template <typename T>
auto random() -> T {
static_assert(std::is_trivial_v<T>, "T must be trivial");
// This is bad. It will instantiate a new mt19937 engine for each type `T`.
//static auto random_device = std::random_device{};
//static auto generator = std::mt19937(random_device());
auto& generator = get_mt19937();
// no need to static
auto distribution = std::uniform_int_distribution<unsigned char>{};
auto return_val = T{};
// reinterpret_cast'ing to std::uint8_t* might not be legal
auto* bytes = reinterpret_cast<unsigned char*>(&return_val);
for (auto i = std::size_t{0}; i < sizeof(T); ++i) {
bytes[i] = distribution(generator);
}
return return_val;
}
For a generic type T
, this may not be not safe.
According to the c++ reference, a reinterpret_cast
is allowed if:
- Any object pointer type T1* can be converted to another object pointer type cv T2*. This is exactly equivalent to static_cast<cv T2*>(static_cast<cv void*>(expression)) (which implies that if T2's alignment requirement is not stricter than T1's, the value of the pointer does not change and conversion of the resulting pointer back to its original type yields the original value). In any case, the resulting pointer may only be dereferenced safely if allowed by the type aliasing rules (see below).
So, you can do the cast from *std::uint8_t
to *T
. But the problem is, when can you dereference the resulting pointer to T
?
Not for every type T
is this allowed.
The same page says that you can dereference to a AliasedType T
if:
AliasedType is std::byte, (since C++17) char, or unsigned char: this permits examination of the object representation of any object as an array of bytes.
So, if you restrict the use of your routine to the std::byte
, char
, or unsigned char
(as mentioned above in the second quote), yes, you can then safely dereference the pointer in the return
statement and your function should be safe.
But, you should rather consider to use std::byte
to store a generic raw-memory buffer.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With