C++ Logo

std-proposals

Advanced search

Re: [std-proposals] std::sizeof_minus_trailing_padding

From: Arthur O'Dwyer <arthur.j.odwyer_at_[hidden]>
Date: Mon, 4 Dec 2023 14:54:34 -0500
On Mon, Dec 4, 2023 at 2:33 PM Thiago Macieira via Std-Proposals <
std-proposals_at_[hidden]> wrote:

> On Sunday, 3 December 2023 23:08:07 PST Frederick Virchanza Gotham via
> Std-Proposals wrote:
> > On Mon, Dec 4, 2023 at 3:02 AM Arthur O'Dwyer wrote:
> > > What I think Thiago meant, and certainly what I mean, is that it's
> > > wrong to use [[no_unique_address]] on the `value` member of
> > > an `optional` or `expected`.
> >
> > I think it's fine so long as you make it very obvious that you're not
> > allowed to use 'memcpy' or 'memset'. Maybe call it something like:
> >
> > class optional_compacted_no_memset { . . . };
>
> That's not the same thing as Arthur said. If you want to add a new class
> that
> has those extra constraints on top of std::optional and std::expected,
> then
> sure. He said it's not acceptable to add the attribute to those classes
> because they don't have and can't have this extra requirement on the
> user's
> template arguments.
>

Right. Basically, there are a bunch of things that an Allocator has to do
if it wants to not-crash — the list of requirements has evolved over
decades — but it's something like:
- It must provide a value_type and .allocate(n) method and so on, of course
- It must be a template, and Allocator<T> must be implicitly convertible
from Allocator<U>
- and in fact it must be able to round-trip T -> U -> T and come out equal
to the original, via *any* unrelated type U
- Move-construction must do the same thing as copy-construction (it mustn't
"null out" the source Allocator object)
- The type must be prepared to be stored as a base class (so it mustn't be
`final` <https://godbolt.org/z/q7crfGsMf>) and/or overlapping subobject
And so on and so forth. The programmer breaks those rules at their peril.
Some of these rules are enforceable via concepts; some are documented in
the Standard; some are only passed down in oral folklore.
Comparators have similar requirements, again evolved over decades, again
involving "you have to expect to sometimes be copied using your
move-constructor" and "you may be stored as a base class and/or overlapping
subobject."

On the other hand, these requirements are *not* part of the story for *ordinary
everyday* types; so templates taking ordinary everyday types — the T in
vector<T>, the T in optional<T> — shouldn't assume that these requirements
are going to be modeled by those Ts. In practice they *will* not be modeled.

> I was also thinking of writing something along the lines of an
> > 'array_compacted' which would be the same as an 'std::array' except
> > that the elements have no padding between them, which would be very
> > useful for minimising Flash usage on a microcontroller -- something
> > like:
>
> You're describing __attribute__((packed)) a.k.a. [[gnu::packed]]. Why not
> use
> that instead?
>

First reaction: "I wouldn't expect __attribute__((packed)) to do anything
to the elements *within* an array!" ( https://godbolt.org/z/7nzev5ejd )
But then I realized Thiago meant the programmer should apply
__attribute__((packed)) to the element's type itself, like this:
struct [[gnu::packed]] OkayWithPacking {
  int i; short s;
  void set(int i) { i = n; }
};
std::array<OkayWithPacking, 10> arr; // Just Works
arr[1].set(42);
And this is important, of course, because `OkayWithPacking::set` needs to
know *at the time it is compiled* whether it's conceivable that `this->i`
is misaligned or not.
You can't just take a type that isn't okay-with-packing and *forcibly pack
it*; that will simply cause a lot of bus errors inside `set`. The type
needs to — this is a motif here — *know how it is going to be used*.

–Arthur

Received on 2023-12-04 19:54:48