C++ Logo

std-proposals

Advanced search

Re: [std-proposals] Impact of defaulted ctor on value initialization

From: organicoman <organicoman_at_[hidden]>
Date: Tue, 11 Jul 2023 17:07:37 +0400
+1
-------- Original message --------From: Arthur O'Dwyer via Std-Proposals <std-proposals_at_[hidden]> Date: 7/11/23 4:52 PM (GMT+04:00) To: std-proposals_at_[hidden], Ofek Shilon <ofekshilon_at_[hidden]> Cc: Arthur O'Dwyer <arthur.j.odwyer_at_[hidden]>, Giuseppe D'Angelo <giuseppe.dangelo_at_[hidden]> Subject: Re: [std-proposals] Impact of defaulted ctor on value initialization On Tue, Jul 11, 2023 at 7:18 AM Jonathan Wakely via Std-Proposals <std-proposals_at_[hidden]> wrote:On Tue, 11 Jul 2023 at 12:11, Jonathan Wakely <cxx_at_[hidden]> wrote:On Tue, 11 Jul 2023 at 11:17, Ofek Shilon <ofekshilon_at_[hidden]> wrote:On Tue, 11 Jul 2023 at 12:53, Jonathan Wakely <cxx_at_[hidden]> wrote:Why should Thingy3() = default; give you that behaviour? It's still trivial and it is noexcept. Why should it prevent Thingy3() from zeroing the members?The phrasing of this question assumes zeroing the members is some natural default and deviating from it requires justification. I believe in C++ it's the other way around: you don't pay for what you don't use, so me & quite a few people I talked to would expect `= default` to consistently not initialize the members, in all initialization forms. But value-initialization zeros things, just like:int i = int();If you don't want that, don't use value-init:int i;Thingy1 t1;[...]Your suggestion would **remove** functionality from the language. It would no longer be possible to have a class with a user-declared copy/move/other constructor and a trivial default constructor, so you would have to pay for functionality (non-trivial default constructor) that you don't want.While I agree that this is a really tricky area and any modifications in this area should be assumed to be very difficult and probably not worth the cost... I also have a different viewpoint from Jonathan's here.The root problem here IMHO is that C++ has two core ideas ("default-initialization" and "value-initialization") which are distinguishable in the Platonic realm; but the syntax corresponding to these ideas is all conflated together. Specifically two flaws:(1) Some primitive types (like `int`) make default-initialization behave differently from value-initialization. It is possible to emulate this behavior in a user-defined class type, but only under very specific restrictions that correspond to leaving specific fields uninitialized at the machine level. So for example if I wanted to define a `class TrappingInt` such that auto i1 = int(); // value-initialized to zero auto i2 = TrappingInt(); // value-initialized to zero int i1; // default-initialized to garbage TrappingInt i2; // default-initialized to a trapping valuewell, there's no way to achieve that in today's C++. This is because the syntax for defining a "default constructor" really does double duty: it defines the default constructor and the, let's say, "value constructor," simultaneously, in an implicit and entangled way. This is very convenient for most programmers, but specifically inconvenient for the rare programmer who wants to emulate `int` as shown above. It means that there is behavior of the primitive types that can't be emulated by user-defined types.(2) Suppose I want to construct a prvalue of a given type, using its {default,value} constructor. There is no core-language syntax for this at all. auto k1 = int(); // OK, value-initialize an int auto k2 = ???; // inexpressible: default-initialize an intIIRC, we recently discussed this problem w.r.t. -ftrivial-var-init=zero and prvalues of types like `std::array`: C++'s syntax lets us write void compute(std::array<int, 1000> scratch_space) { ... immediately overwrite whatever's in scratch_space ... } void test() { compute(std::array<int, 1000>()); }but that wastefully zeroes out 4000 bytes of memory before calling `compute`. What we actually wanted to express here was "pass a default-initialized std::array to `compute`," but C++ doesn't give us any syntax to express that.This deficiency-of-syntax leads to things like `S2` at the end of this blog post:https://quuxplusone.github.io/blog/2023/06/22/psa-value-initialization/ struct S2 { explicit S2() {} int T::*mf; };`S2` has a user-defined default constructor, so even though it is in the Platonic[*] sense "trivially default-constructible," the compiler cannot prove that it is.We could =default its default constructor; but if we do that, then it becomes in the Platonic sense "non-trivially value initializable," which is worse (as we can see from the codegen of `f2` in the blog post).We'd like a way to express to the compiler that a type like `S2` is trivially default-constructible and trivially value-initializable, so that we get the right answer from `is_trivially_default_constructible_v` and good codegen for `return S2()`. Right now, it's "pick one or the other." Because there's a single-dimensional control knob (the user-declared default constructor) controlling two different dimensions (default-initialization behavior and value-initialization behavior).Could we "fix" this by adding core-language syntax for a "value-initialization constructor"? Maybe, but I'll repeat: this area is very tricky and your default assumption should be that any change here will be very very difficult and almost certainly not worth the cost. Please don't assume that anything in this area will admit a "simple fix"; because then you'll just be wrong. ;)(I owe a blog post on the general topic of the gap between what the front-end can prove based on syntax, and what the optimizer is allowed to generate based on deeper Platonic ideas. The gap is created by the impedance mismatch between ideas and syntax; we can often close the gap by adding attributes such as P1144's [[trivially_relocatable]], Clang's proposed [[equality_is_memberwise]], and so on.)–Arthur[* — When I say a type is trivially fooable in the Platonic sense, I mean that a sufficiently smart optimizer could in theory replace the type's foo operation with fooing the individual bytes of its object representation, without loss of correctness. When I say the compiler can prove that it's trivially fooable, I mean pragmatically, in the front-end, such that std::is_trivially_fooable_v<T> will evaluate to true at compile time.]

Received on 2023-07-11 13:07:51