It seems it is a trivial task, but I cannot find the way to do it properly without UBs and still have a compile time validation.
I need to implement the linked list of different types. The problem is that I cannot find how to calculate the memory size needed for the MultipleValuesEntry structure, that contains a flexible array inside.
In case if I use offsetof, I get a warning.
#include <cstddef>
enum class EntryType
{
SingleElement,
MultipleElements
};
struct Entry
{
Entry* next_;
EntryType type_;
};
struct SingleValueEntry : Entry
{
int value_;
};
struct MultipleValuesEntry : Entry
{
int values_[1]; // < Open array
static constexpr size_t estimateSize(size_t valuesCount) noexcept
{
return offsetof(MultipleValuesEntry, values_)
+ sizeof(values_[0]) * valuesCount;
// ^^^^^ ‘offsetof’ within non-standard-layout type warning
}
};
The reason why I want to have a flexible array and not for instance std::vector, is I want the memory for the flexible part be allocated close the the rest of the data members.
In order to solve the warning, instead of inheritance I can have EntryType as a first data member in the MultipleValuesEntry.
struct MultipleValuesEntry
{
Entry entry_;
int values_[1]; // < Open array
static constexpr size_t estimateSize(size_t valuesCount) noexcept
{
return offsetof(MultipleValuesEntry, values_)
+ sizeof(values_[0]) * valuesCount;
}
};
void process(Entry* entry)
{
if (entry->type_ == EntryType::MultipleElements)
{
auto mpe = reinterpret_cast<MultipleValuesEntry*>(entry); // pointer-interconvertible
// consume `mpe`
}
}
Then when I need to consume the entry, I can perform a reinterpret_cast from EntryType* to MultipleValuesEntry* because two pointers are pointer-interconvertible. The problem is there is no compile time validation that it is safe to perform reinterpret_cast. If someone in future change the structure layout, they won't get a compile time error and app will crash in production.
So, the only choice I've got is to implement my own offsetof macro, but it seems it is not that trivial. There are a lot of pitfalls.
There are a lot of articles around. Chat GPT suggests to use std::aligned_storage:
static constexpr size_t estimateSize(size_t valuesCount) noexcept
{
// Calculate the offset manually using aligned storage
using StorageType = std::aligned_storage<sizeof(MultipleValuesEntry), alignof(MultipleValuesEntry)>::type;
StorageType storage;
auto* entryPtr = reinterpret_cast<MultipleValuesEntry*>(&storage);
size_t values_offset = reinterpret_cast<char*>(entryPtr->values_) - reinterpret_cast<char*>(entryPtr);
// Calculate the total size
return values_offset + sizeof(values_[0]) * valuesCount;
}
I'm not sure if it is legal to access the uninitialized storage as a MultipleValuesEntry. I do not trust this tool. It often suggests some b.sh..t and when I point on issue, it apologies and suggests another one.
So from the low level (ASM) point of view it is piece of cake, but C++ is not that trivial. Please help.
std::vector.