C++ Logo

std-proposals

Advanced search

Re: [std-proposals] A type trait to detect if value initialization can be achieved by zero-filling

From: Jason McKesson <jmckesson_at_[hidden]>
Date: Mon, 30 Jan 2023 01:04:59 -0500
On Sun, Jan 29, 2023 at 8:36 PM Giuseppe D'Angelo via Std-Proposals
<std-proposals_at_[hidden]> wrote:
>
> Hello,
>
> Il 29/01/23 22:11, Jason McKesson via Std-Proposals ha scritto:
> > On Sun, Jan 29, 2023 at 3:41 PM Giuseppe D'Angelo via Std-Proposals
> > <std-proposals_at_[hidden]> wrote:
> >>
> >> Hello,
> >>
> >> As per subject, I've been working on a proposal for a standardized way
> >> to detect if a trivially default constructible type can be value
> >> initialized by using `memset(0)` on some suitable storage.
> >>
> >> A draft is available here:
> >>
> >> https://isocpp.org/files/papers/D2782R0.html
> >>
> >> As usual, any early feedback is very much appreciated. I am not 100%
> >> convinced about the name of the trait, as I'm trying to model something
> >> for which there isn't already an established name/concept. I hope the
> >> discussion/bikeshedding can wait after everything else has been ironed
> >> out...
> >
> > I don't like that the set of such types is so broadly
> > implementation-defined. We need something more concrete.
>
> Thank you for the feedback!
>
> So, rather than just hand-waving the semantics, would you prefer a
> stronger classification of such types (say, in [basic.types.general] and
> [class.prop]) and then the trait simply refers to the classification? I
> guess I can work with that.
>
> >
> > I would suggest that a type is "trivially zero-initializable" if:
>
> Without going immediately into naming bikeshedding, I was deliberately
> steering away from _anything_ with "zero-init" in its name, as "zero
> initialization" has well-established semantics which are not what I'm
> looking for. To zero-initialize a pointer does not necessarily mean fill
> its storage with zeroes :-(

That's why it's called "trivial zero initialization". It's saying that
the value representation of a zero-initialized object of this type
contains all zero bytes. A type that undergoes zero-initialization but
the value representation of that type is not all zeros does not
undergo *trivial* zero initialization. It's similar to how trivial
copyable types are copied via bytewise copy, while non-trivial copying
involves calling actual code.

> "(trivially) value-initializable by zero-filling" is a complete mouthful
> but would be spot-on.
>
>
> > 1. If it is a class type:
> > 1a. It is an implicit lifetime type
> > 1b. All of its non-static data members and base classes are
> > themselves implicit lifetime types
>
> I'll answer the other message regarding implicit lifetime types...
>
>
> > 2. It is an arithmetic type
> > 3. It is an enumeration type
> > 4. It is `nullptr_t`
>
> Devil's advocate: are there any guarantees that this is the case? Is an
> implementation allowed to use (uint)-1 as object representation of
> objects of type `nullptr_t` and to actually implement
> operator==(nullptr_t a, nullptr_t b) as `return a==b;`?
>
> I'd just fold it into the "implementation defined".

I see no reason to *preemptively* do that. The point of my suggestion
is to allow people to write types that they *know* are trivially zero
initializable. This isn't for code where you have different code
paths, but for code where you require the type to support trivial zero
initialization or you error out.

If there is a problem with zeroing out a `nullptr_t`, then someone
will bring that up when the paper is presented.

> 5. The other fundamental types are all implementation defined.
>
> > However, I would suggest that it should be equally true/false for all
> > pointers of the same category: object pointers, function pointers, and
> > member pointers. Either all object pointers are
> > trivially-zero-initializable or none of them are. Etc.
>
> Is there any reason for this? On Itanium, pointers to objects and to
> functions can be zero-filled. Pointers to data members cannot. Why not
> having the trait giving the factually correct answer? Surely one would
> want a vector of `string_view`s to be zero-filled.

I may not have explained what I meant very well.

There are 3 categories of pointers: object pointers, function
pointers, and member pointers. All pointers of a *particular* category
have to give the same answer. So all object pointers are either
trivially zero initializable or not. But all function pointers could
have a *different* answer. Etc. So on Itanium, they could have
function and object pointers be trivially zero initializable, but not
member pointers.

What it can't do is have `int*` be trivially zero initializable but
`float*` not be.

> > 6. No reference types are trivially-zero-initializable.
> >
> > Also, how does Itanium-based code work if you have a member pointer in
> > static storage that gets zero-initialized? Does something have to
> > manually fill in the -1s at runtime, or does an object containing such
> > a member pointer have to take up space in static executable storage?
>
> Could you please elaborate on this question? "Normally" it ends up
> eating space in .data, and not folded in .bss. Whether it's filled at
> compile time or runtime depends on if you're initializing it with a
> constant expression or not...?

We're talking about static storage objects that are being
zero-initialized. My question was whether the executable puts the
object in memory that is automatically zeroed out (the .bss section)
and then fills in -1s into places where they are needed, or loads a
pre-initialized image from the executable (the .data section). I was
asking by describing the process because I'd forgotten what those
sections were formally called.

On Sun, Jan 29, 2023 at 8:36 PM Giuseppe D'Angelo via Std-Proposals
<std-proposals_at_[hidden]> wrote:
>
> Il 29/01/23 22:19, Jason McKesson via Std-Proposals ha scritto:
> > It also makes a lot of sense to be able to zero out some memory and
> > then invoke `start_lifetime_as` upon the objects in it. And some
> > implicit lifetime types are not trivially default constructible, but
> > they can still have `start_lifetime_as` invoked upon them. So at the
> > very least, it should be either implicit lifetime or trivially default
> > constructible.
>
> I'm really really really not sure about the implications of adding
> implicit lifetime types to the mix. What you say above makes lots of
> sense, but how in the world is the compiler supposed to know if
>
> struct Foo {
> int i;
> Foo(); // non-trivial, user-provided, out of line, ...
> };
>
> can or cannot be value-initialized by zero-filling storage? Foo is an
> implicit-lifetime class: it has a trivial _copy_ constructor and trivial
> destructor.

That's a fair point. A type cannot undergo trivial zero initialization
if it cannot undergo zero initialization at all. Even if force-feeding
it zeros is a valid value representation, getting to that state is not
"zero initialization".

Received on 2023-01-30 06:05:30