Date: Tue, 11 Jul 2023 10:52:51 +0100
On Tue, 11 Jul 2023 at 10:45, Ofek Shilon via Std-Proposals <
std-proposals_at_[hidden]> wrote:
> The standard says [dcl.init]:
> http://eel.is/c++draft/dcl.init#general-9.1.1
>
> "To value-initialize an object of type T means:
> (9.1) if T is a (possibly cv-qualified) class type ([class]), then
> (9.1.1) if T has either no default constructor ([class.default.ctor]) or a
> default constructor that is *user-provided* or deleted, then the object is
> default-initialized;
> ...
> (9.3)
> otherwise, the object is zero-initialized."
>
> (note the emph on *user-provided*, as opposed to user-declared).
> This causes very surprising behavior like the following (
> https://godbolt.org/z/rxYcsYdTd):
>
> #include <iostream>
> using namespace std;
>
> int main() {
>
> struct Thingy1 { // No user provided default ctor
> int i;
> };
> cout << Thingy1().i << endl; // Zero
> Thingy1 t1;
> cout << t1.i << endl; // Junk
>
> struct Thingy2 {
> Thingy2() {}; // User provided default ctor
> int i;
> };
> cout << Thingy2().i << endl; // Junk
>
> struct Thingy3 {
> Thingy3() = default; // user DECLARED, not provided
> int i;
> };
> // This is the surprising bit:
> cout << Thingy3().i << endl; // Zero again!
> }
>
> ----------------------------
>
> I've chased down the origin of this to defect report 1368 from 2011 (
> https://cplusplus.github.io/CWG/issues/1368.html). Quote:
>
> " struct A {
> int i;
> A() = default;
> A(int i): i(i) { }
> };
> value-initialization leaves A::i uninitialized. This seems like an
> oversight."
>
> I don't understand the "This seems like an oversight" bit. I spoke with
> quite a few people who were equally surprised and annoyed by this behavior
> - it seems the common expectation is that `Thingy3() = default` would
> express an intention identical to `Thingy3() {}`.
>
No. It should be more like Thingy1.
If you write Thingy2() { } then you have explicitly said you don't want the
members initialized, *even if* the object is value-initialized. This means
that it's very different from Thingy1. You've also said you want a
non-trivial default constructor, and that it's noexcept(false).
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?
> Now before I try to write a paper to promote (what I perceive to be) a fix
> - am I missing something? Is there reason to make A::i zero-initialized
> despite the intention expressed in `A() = default` ?
>
You say "despite the intention" and I say "because of the intention". The
defaulted constructor should not have the effects you want.
Sometimes you need to add `T() = default;` so that the type is default
constructible, e.g. if there are any other user-declared constructors. If
defining it as defaulted behaved how you want, then there would be no way
to say "it's default constructible, but I still want it to be trivial". If
you do want it to be non-trivial, you can already do that, as you did for
Thingy2.
std-proposals_at_[hidden]> wrote:
> The standard says [dcl.init]:
> http://eel.is/c++draft/dcl.init#general-9.1.1
>
> "To value-initialize an object of type T means:
> (9.1) if T is a (possibly cv-qualified) class type ([class]), then
> (9.1.1) if T has either no default constructor ([class.default.ctor]) or a
> default constructor that is *user-provided* or deleted, then the object is
> default-initialized;
> ...
> (9.3)
> otherwise, the object is zero-initialized."
>
> (note the emph on *user-provided*, as opposed to user-declared).
> This causes very surprising behavior like the following (
> https://godbolt.org/z/rxYcsYdTd):
>
> #include <iostream>
> using namespace std;
>
> int main() {
>
> struct Thingy1 { // No user provided default ctor
> int i;
> };
> cout << Thingy1().i << endl; // Zero
> Thingy1 t1;
> cout << t1.i << endl; // Junk
>
> struct Thingy2 {
> Thingy2() {}; // User provided default ctor
> int i;
> };
> cout << Thingy2().i << endl; // Junk
>
> struct Thingy3 {
> Thingy3() = default; // user DECLARED, not provided
> int i;
> };
> // This is the surprising bit:
> cout << Thingy3().i << endl; // Zero again!
> }
>
> ----------------------------
>
> I've chased down the origin of this to defect report 1368 from 2011 (
> https://cplusplus.github.io/CWG/issues/1368.html). Quote:
>
> " struct A {
> int i;
> A() = default;
> A(int i): i(i) { }
> };
> value-initialization leaves A::i uninitialized. This seems like an
> oversight."
>
> I don't understand the "This seems like an oversight" bit. I spoke with
> quite a few people who were equally surprised and annoyed by this behavior
> - it seems the common expectation is that `Thingy3() = default` would
> express an intention identical to `Thingy3() {}`.
>
No. It should be more like Thingy1.
If you write Thingy2() { } then you have explicitly said you don't want the
members initialized, *even if* the object is value-initialized. This means
that it's very different from Thingy1. You've also said you want a
non-trivial default constructor, and that it's noexcept(false).
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?
> Now before I try to write a paper to promote (what I perceive to be) a fix
> - am I missing something? Is there reason to make A::i zero-initialized
> despite the intention expressed in `A() = default` ?
>
You say "despite the intention" and I say "because of the intention". The
defaulted constructor should not have the effects you want.
Sometimes you need to add `T() = default;` so that the type is default
constructible, e.g. if there are any other user-declared constructors. If
defining it as defaulted behaved how you want, then there would be no way
to say "it's default constructible, but I still want it to be trivial". If
you do want it to be non-trivial, you can already do that, as you did for
Thingy2.
Received on 2023-07-11 09:53:07