Date: Tue, 12 Dec 2023 00:22:21 -0500
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_at_[hidden]> 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_at_[hidden]> 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_at_[hidden]
>> https://lists.isocpp.org/mailman/listinfo.cgi/std-proposals
>>
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_at_[hidden]> 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_at_[hidden]> 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_at_[hidden]
>> https://lists.isocpp.org/mailman/listinfo.cgi/std-proposals
>>
Received on 2023-12-12 05:22:31