On Tue, Jul 11, 2023 at 9:22 AM Marcin Jaczewski <marcinjaczewski86@gmail.com> wrote:
wt., 11 lip 2023 o 14:52 Arthur O'Dwyer via Std-Proposals <std-proposals@lists.isocpp.org> napisał(a):
>
> 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 value
> well, 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 int
> IIRC, 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.

And this have another problem that `int i;` can be source of UB if was
done unintentionally, for me we could fix both problems by introducing
new keyword similar to `nullptr`,
like `default_init` or something like this, then we could make:

class F
{
   int i;
   F(std::default_init_t) = default; //make default init for `i`
   F() = default; // value init for `i`
};

int main()
{
    int i[10] = default_init; // same as `int i[10];`
    std::array<F, 10> f = { default_init }; // or `std::array<F, 10> f
= default_init;`

Hmm, normally aggregate-initialization zero-fills the unspecified values. So
    int a[10] = default_init;
would mean "garbage-fill the entire array object," but
    int a[10] = {default_init};
would mean "garbage-fill a[0], and then zero-fill a[1] through a[9]."  Right?  This is fine, but awfully subtle.
Likewise, 
    std::array<F, 10> f = { default_init };
    std::array<F, 10> f = default_init;
would presumably do different things, not the same thing (as you implied).

Interesting thing will be when we do something like:

class SafeInt {
    int i;
    explicit SafeInt(std::default_init_t) = default;
};
int main() {
    SafeInt i; //error, it uses explicit constructor!
    SafeInt j = SafeInt(default_init); //ok, no initialization is done
};

This syntax seems suboptimal to me. I observe:
- `SafeInt i;` is not an implicit conversion; it's perfectly well allowed to use an `explicit` constructor. So `explicit` isn't the keyword you mean, here. What `explicit` disallows is `SafeInt i = {};` — the implicit [scare-quotes] "conversion" of `{}` to an expression of type `SafeInt`.
- Your use-case, IIUC, wants to forbid `SafeInt i;` but explicitly permit `SafeInt j = SafeInt(default_init)`. Presumably you'd also like to permit `SafeInt j = default_init`, since it's equally explicit about what's happening there. This smells more like a case for a tag type than anything specifically related to default-initialization. Like, today, we could say

    struct default_init_t { explicit default_init_t() = default; };
    inline constexpr default_init_t default_init;

    class SafeInt {
    public:
        SafeInt(default_init_t) {}
        SafeInt(int i) : i_(i) {}
    private:
        explicit SafeInt() = default;
        int i_;
    };
    SafeInt si1;  // ill-formed
    SafeInt si2 = 0;  // OK
    SafeInt si3 = default_init;  // OK, i_ is garbage
    SafeInt si4 = SafeInt(default_init);  // OK, i_ is garbage
    SafeInt si5 = SafeInt();  // ill-formed (!!)
    void f6(SafeInt); ... f6(default_init);

The two problems I see with today's solution are:
- si5 is ill-formed
- We would like si3 to be well-formed but f6 to be ill-formed; we can't get that without a core-language change, since both are syntactically implicit conversions

Your idea, to make `default_init` a keyword with semantics different from the above `::default_init`, clearly has the potential to help with one or both of these problems; but I don't think it quite gets there yet, does it?

–Arthur