For the TokenInfo example, and in case of most fixed layout types, I think there is a better approach than playing with attributes/pragma: encapsulation and abstraction, as always.
Instead of exposing data members, provide accessor functions (ie, getters and setters), and use unsigned char arrays to hold unaligned members (or whatever other object type that can be properly align right after the previous member).
std::unaligned is kind of similar to this, as it is an abstraction over the char array storage.

As far as I know, types with strictly defined layout mostly occur as low level data structures, often defined by OS API or hardware or ISA layer, and only consist of integral types, hence the data structure itself is trivially copiable. I don't see much use cases for for having anything else than trivially copiable types as unaligned. Especially, it might just impact performance negatively.

Julien V.


On December 11, 2023 10:57:42 p.m. EST, connor horman via Std-Proposals <std-proposals@lists.isocpp.org> wrote:
The proposed assignment operator has undefined behaviour, because you're referring to an object of type `T` in storage which is not the appropriate size for `T`. `std::memcpy` cannot create an object of type `T` in that storage, because an object of type `T` can only exist in storage that has size and alignment `sizeof(T)` and `alignof(T)`. `__datasizeof(T)` I believe excludes tail padding, but the compile may make use of the full `sizeof(T)`. `unaligned<T>` could certainly internally contain only `__datasizeof(T)` bytes, but any time you copy out `T`

Also, trivially_relocatable is further incorrect, because even if you did use the correct size, implicit creation of objects requires that `T` be an implicit lifetime type. Even if the storage was appropriately sized, a trivially relocatable T would not necessarily be an implicit lifetime type. `std::unique_ptr` would be a counter example - it is not an aggregate type, and has neither a trivial eligible constructor nor a trivial destructor. While the definition is certainly more expansive than trivially-copyable, I'm not sure it is meaningfully more expansive, given the special member functions all requiring that the underlying one from `T` is trivial.





On Mon, 11 Dec 2023 at 21:14, Thiago Macieira via Std-Proposals <std-proposals@lists.isocpp.org> wrote:
On Monday, 11 December 2023 20:25:19 EST Frederick Virchanza Gotham via Std-
Proposals wrote:
>     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.

Strictly as above, no, it's still trivially relocatable. You need to add the
copy & move constructors to make it no longer trivially-relocatable. I assume
you omitted them for brevity.

> 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.

That's not a trivial relocation. That's a trivial *copy*. The original object
is still there. It makes sense to add the ability to relocate out of a
std::unaligned. Semantically, it does not make sense to impose the requirement
on the contained type.

That said, since the type must be trivially copyable and movable, by
construction it will also be trivially relocatable. The three trivial
operations are the same: a memcpy.

> 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

I think that's an unnecessary distraction for now. std::atomic has them, but
let's focus on the basics first.

> 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;

I think that's a bad idea. This is a completely distinct type than the
original TokenInfo. It may make sense to have a std::deep_unaligned, which
would need to either wait for reflection or would require compiler support. At
which point it may be a keyword instead of a class or an attribute.

> Furthermore, I think that 'unaligned' should be compatible with
> 'optional'

It is compatible.

> 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 );

There's no such thing as std::take_ownership. You'll need to describe the
intent here, as we're not very good at diving it. What does it return? And if
it's not a SomeClass, why would std::unaligned have a constructor that can
receive it?

> 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();
>     }

unalgined's destructor ought to be trivial. So the only implementation is

 = default

Even if not, your suggestion is not acceptable. Either the contained type has
a trivial destructor (as discussed before), in which case this is a no-op and
thus wholly unnecessary, or it isn't trivial and in which case the address may
matter. If it does matter, then the suggested destructor will cause
misbehaviour.
If the implicit-lifetime type issue does not apply, then a trivially_relocatable type would by definition not have misbehaviour after you move the type into `unaligned`.  

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

Can't do it, because the base sub-object can't exist inside the derived
object, given the alignment constraints.

--
Thiago Macieira - thiago (AT) macieira.info - thiago (AT) kde.org
   Software Architect - Intel DCAI Cloud Engineering



--
Std-Proposals mailing list
Std-Proposals@lists.isocpp.org
https://lists.isocpp.org/mailman/listinfo.cgi/std-proposals