On Wed, 18 Jan 2023 at 21:18, Frank B. Brokken via Std-Discussion <std-discussion@lists.isocpp.org> wrote:
Dear Edward and Brian,

Thanks for your replies! Still, there are some open ends: see below.

>From Edward's last e-mail:

> > But before the exception is thrown its d_value has received a value,
> > which is then displayed in its function-try-block.
>
> Well, yes, but you aren't allowed to access that value, both because that
> int object is outside its lifetime, and also because you aren't allowed to
> refer to the int object to access its value.
>
> .... The Standard has cases where
> a rule is more general than absolutely necessary for the sake of simplicity.
>
> > I'm
> > still struggling with the question why referring to its d_value data member
> > in the constructor's function-try-block implies undefined behavior
>
> Your d_value might be a member via a virtual base class of Int. It isn't,
> but it might be.
>
> > But please advise if I should address the question
> > to 'StackOverflow C++'
> >

> No, you're in the right place. You might get an answer on SO, but they do
> have a tendency to see the Standard as not to be questioned. Here is a bit
> more relaxed.


About the open ends: on the one hand you write

(1)
> you aren't allowed to access that value, both because that
> int object is outside its lifetime, and also because you aren't allowed to
> refer to the int object to access its value.

and on the other hand:

(2)
> .... The Standard has cases where
> a rule is more general than absolutely necessary for the sake of simplicity.


The 2nd part of (1) looks slightly weird: I can't use that d_value data member
just because I'm not allowed to use it?

You can't access it because you aren't allowed to refer to it. These are two different terms of art - sorry!

But that returns us to the essence of
my question: there should be a reason *why* this restriction applies. With
things like base classes and already destroyed class type data members that's
clear, but for basic type data members not. The 1st part also isn't completely
transparent.

If you mean access, one compelling reason is to allow dynamic safety checks to detect possible bugs (e.g. the sanitizers, NaT on platforms with such bit patterns, or in general with "poison" bit patterns).

If you mean referring to such members, there's some sympathy to permitting referring to data members not of virtual bases during their lifetime - "address offset calculations" in the words of CWG 597.

So maybe it's just (2)? Maybe the rule is too general 'just to be sure',
although there may be situations where data members of classes can be
accessed.

Also note that "members" includes member functions as well as data members. This is relevant when such functions can be virtual.

Section 6.7.3  Lifetime [basic.life] states:

    The lifetime of an object of type T begins when:
       (1.1)    — storage with the proper alignment and size for type T is
              obtained, and
       (1.2) — its initialization (if any) is complete

which is nice, but other than defining the term 'lifetime' it doesn't specify
what that implies. (1.1) is clear: no storage, no T, but what about (1.2)?

That second bullet currently reads "its initialization (if any) is complete (including vacuous initialization) ([dcl.init]),". Does that help?

Wrt ending lifetimes, 6.7.3. item 1 states:

        The lifetime of an object o of type T ends when:
        (1.3)    — if T is a non-class type, the object is destroyed,

That's the situation of my d_value data member: a non-class type. But d_value
still exists when the try-block handler is reached as the Int object's memory
is still available at that point.

The object exists, but its memory could have been poisoned in some way, either in passing or intentionally to help detect use-after-free bugs.

While:
        (1.4)    — if T is a class type, the destructor call starts,
does not apply, since there's no destructor called by the throwing
constructor. Thus
        (1.5)    — the storage which the object occupies is released, or is
          reused by an object that is not nested within o (6.7.2).
happens when, e.g., the stack frame of the function defining an object
whose constructor throws ends. But again, that doesn't yet hold true when a
constructor throws after having initialized some of its basic type data
members.

Section 6.7.3 also refers to sections 11.9.3 and 11.9.5. In a note it states:

    3 [Note 1 : 11.9.3 describes the lifetime of base and member subobjects. —
          end note]

then, 11.9.3. states:

    11.9.3    Initializing bases and members            [class.base.init]

    1 In the definition of a constructor for a class, initializers for
    ...  non-static data members can be specified by a ctor-initializer

which is exactly what happens in my 'class Int' example. In other words,
d_value has been initialized, and according to 1.4 and 1.5 in 6.7.3. that
d_value data members remains available when the constructor's
function-try-block handler is executed.

Section 11.9.5. doesn't quite clarify why d_value can't be used:

    11.9.5 Construction and destruction [class.cdtor]

    For an object with a non-trivial constructor, referring to any non-static
    member or base class of the object before the constructor begins execution
    results in undefined behavior.

But that's not the issue. The constructor has begun its execution. There's
also its item 5:

    5 A program may end the lifetime of an object of class type without
          invoking the destructor, by reusing or releasing the storage as
          described above.

In the case of my example the destructor isn't invoked, and since d_value
lives in Int's object which lives in main's stackframe it's there, it has been
initialized and so it remains unclear as to why it can't be used in the
constructor's function-try-block handler.


So thanks again for your replies. Unfortunately, for now I'm still wondering
why d_value in my example cannot be used by the constructor's
function-try-block handler, except maybe for that item (2) you mentioned:

> .... The Standard has cases where
> a rule is more general than absolutely necessary for the sake of simplicity.


Finally:

> Well, that doesn't matter that much; there's lots of UB that isn't detected
> by any compiler. Although it does weigh towards suggesting that it might be
> worth considering relaxing that UB.

I of course 100% agree with your 1st sentence: If I bike around in my area I
see lots of white swans. Even if I see hundreds of them that doesn't of course
mean that there aren't any black ones :-)

Wrt to your 2nd sentence: well, who knows... ;-)

Kind regards,

--
    Frank B. Brokken
    (+31) 6 5353 2509
    PGP Key Fingerprint: DF32 13DE B156 7732 E65E  3B4D 7DB2 A8BE EAE4 D8AA
--
Std-Discussion mailing list
Std-Discussion@lists.isocpp.org
https://lists.isocpp.org/mailman/listinfo.cgi/std-discussion