In C++20, can we pack together header and data objects and access the data from the header by offset?

Viewed 141
struct header
{
    int a1;
    int a2;
    // ...;

    std::byte * get_data_bytes()
    {
        return align_up<data>( // make sure alignment requirements are met
                    reinterpret_cast<std::byte *>(this) + sizeof(*this)); 
       // maybe std::launder around the reinterpret_cast (only) is needed?
    }

    data & get_data()
    {
        return *std::launder(reinterpret_cast<data *>(get_data_bytes()));
    }

    void use_data()
    {
        get_data().use();
    }
};

void example()
{
    alignas(header) std::byte storage[/* plenty of space*/]; 
    
    auto h = new (storage) header;
    new (h->get_data_bytes()) data;
    
    h->use_data(); // Does this eventually cause a UB?
}

Is this possible without UB? If not, is there an alternative? The requirement is that data is not a subobject of the header and there is no pointer/reference to the data from the header to avoid additional indirection. This could maybe be possible with flexible empty array but I don't think these are in the standard.

1 Answers

The only thing that is even hypothetically UB is whether reinterpret_cast<std::byte *>(this) is a pointer to one of the bytes in the array or not. But you can std::launder it to ensure that it is. Indeed, laundering to/from byte pointers is sufficiently useful that you could make template functions for them:

template<typename T>
std::byte *to_byte_ptr(T *ptr)
{
  return std::launder(reinterpret_cast<std::byte*>(ptr));
}

template<typename T>
T *from_byte_ptr(std::byte *ptr)
{
  return std::launder(reinterpret_cast<T*>(ptr));
}

Everything else is perfectly fine. You created header in storage provided by a byte array, so there are std::byte objects there accessible via launder. And since those std::byte objects are in an array, you can do pointer arithmetic on them. And while those std::byte objects don't actually point to the objects they provide storage for, if you have a pointer with the same address as the desired object, you can launder it to retrieve a pointer to that object.

Related