C++ Logo

std-proposals

Advanced search

Re: [std-proposals] [[packed]] std::unaligned

From: Frederick Virchanza Gotham <cauldwell.thomas_at_[hidden]>
Date: Tue, 12 Dec 2023 01:25:19 +0000
On Mon, Dec 11, 2023 at 8:35 PM Arthur O'Dwyer wrote:
>
> Thanks :) but I don't think `is_trivially_relocatable` is useful here. You never relocate-out-of (nor into) a `T` here. And the requirements on `T` are much stricter than "trivially relocatable"; for example, you actually need it to be trivially destructible, or else `unaligned<T>` won't have any destructor.
> For example: I don't think it makes sense to talk about `std::unaligned<std::string>`. I don't know what the semantics of that would be.


You've done a lot more work on trivial relocatability than I have, but
anyway let me try tease this out.

Let's say I make a new string class, something like
std::static_string, and it looks something like:

    struct static_string {
        char buf[256u];
    };

Of course, such a class would be trivially relocatable. But let's say
now that I allow the null character to be just another character in
the string, so now I need an extra member to indicate how long the
string is:

    struct static_string {
        char buf[256u];
        unsigned len;
    };

With this change, the class is still trivially relocatable. But let's
throw a spanner in the works. Instead of having a member named 'len',
we instead store a pointer to the last character, as follows:

    struct static_string {
        char buf[256u];
        char *plast;
    };

And so if the string is 10 characters long, then 'plast' will be equal
to 'this + 10 - 1'. The class is no longer trivially relocatable.

Arthur you said in your last post that I "never relocate-out-of (nor
into) a `T`", but if you look at my implementation of the assignment
operator (which I implemented as a template function), I do a memcpy
to a suitably-aligned T-sized buffer and then perform an operation on
it -- it is for this reason that I need T to be trivially relocatable.

You also mentioned that it must have a trivial destructor but I think
this matter is covered by Connor's use of:

        ~unaligned() requires std::is_trivially_destructible_v<T> = default;

...because if I do the following:

    class cannot_destroy { ~cannot_destroy(){} };
    unaligned<cannot_destroy> monkey;

then I get the following compiler error:

    <source>:56:31: error: use of deleted function
'unaligned<T>::unaligned() requires
!(is_trivially_default_constructible_v<T>) [with T = cannot_destroy]'
    56 | unaligned<cannot_destroy> uncd;

So maybe this compiler error is good enough?

Anyway I've added a load of binary operators and unary operators to my
previous code (adapted from Connor's code), and here's what I've got
now:

      https://godbolt.org/z/z7MGMnzre

But even after we put together a robust implementation of
std::unaligned (and I think we're getting close to it), I still want
to be able to do:

    struct TokenInfo {
        long unsigned a;
        char b;
        long unsigned c;
        char d;
    };

    typedef struct TokenInfo [[packed]] TicketInfo;

and have the compiler treat it as though I had written:

    struct TokenInfo {
        long unsigned a;
        char b;
        long unsigned c;
        char d;
    };

   struct TicketInfo {
        unaligned<long unsigned> a;
        unaligned<char> b;
        unaligned<long unsigned> c;
        unaligned<char> d;
    };

Furthermore, I think that 'unaligned' should be compatible with
'optional' so that you can do the following:

    std::optional<SomeClass> monkey;
    monkey.emplace(1,2,3,56.7L);
    std::unaligned<SomeClass> donkey( std::take_ownership_t(), monkey );

After these three lines of code, 'monkey' no longer has a value, and
so the 'donkey' object is now responsible for destroying the SomeClass
object (and in this case I would not require T to be trivially
destructible). Therefore the destructor of 'unaligned' would have to
do something like:

    ~unaligned(void)
    {
        alignas(T) std::byte buf[ __datasizeof(T) ];
        T &tmp = *static_cast<T*>(static_cast<void*>(buf));
        std::memcpy(&tmp, &_m_data.front(), __datasizeof(T));
        tmp.~T();
    }

Also another thing... I would want the 'unaligned' class to be derived
from a base class such as:

    class unaligned_base {};
    class unaligned : public unaligned_base {};

and for as much of the implementation as possible to be put inside
'unaligned_base', so that in the future we could make another type
called 'unaligned_ref' (derived from 'unaligned_base'), which takes
control of the buffer you supply to it in its constructor.

Received on 2023-12-12 01:25:24