C++ Logo

std-discussion

Advanced search

Re: Is [[indeterminate]] not applicable to data members?

From: Thomas Köppe <tkoeppe_at_[hidden]>
Date: Sat, 15 Mar 2025 22:55:06 +0000
Thanks a lot to Brian. I think he captured the point very well. I believe
we discussed the choice of not having the attribute on members, but I'm
afraid that explanation didn't make it into the paper and should have.
Indeed, the attribute disables a safety feature, and so allowing it to
appear where a user can't reach it would somewhat undermine the attempt to
improve safety. The opt-out is still available for situations where a use
case warrants it, but it needs to be at place where the relevant variables
are declared. Classes for which the opt-out promises significant
performance improvements could document that users can benefit from
applying the opt-out.

One could of course imagine revisiting this decision, but that was the
rationale for the status quo.

Thanks!

Thomas

On Thu, 13 Mar 2025 at 20:21, Lénárd Szolnoki <cpp_at_[hidden]>
wrote:

>
>
> On 13 March 2025 16:00:20 GMT, Brian Bi via Std-Discussion <
> std-discussion_at_[hidden]> wrote:
> >On Thu, Mar 13, 2025 at 6:00 AM Jens Maurer via Std-Discussion <
> >std-discussion_at_[hidden]> wrote:
> >
> >>
> >>
> >> On 13/03/2025 05.16, Yongwei Wu via Std-Discussion wrote:
> >> > On Wed, 12 Mar 2025 at 10:46, Yongwei Wu <wuyongwei_at_[hidden]
> <mailto:
> >> wuyongwei_at_[hidden]>> wrote:
> >> >
> >> > On Tue, 11 Mar 2025 at 23:11, Brian Bi <bbi5291_at_[hidden]
> <mailto:
> >> bbi5291_at_[hidden]>> wrote:
> >>
> >> > I'm uncomfortable with the idea of a class author being able
> to
> >> override the user's choice to *not* declare a local variable
> >> [[indeterminate]], by placing [[indeterminate]] on the declaration of a
> >> non-static data member and thus making part of the object's storage
> >> indeterminate anyway. Note that even if it's just in a private member,
> >> there will still be UB if the object is used as the source of `memcpy`.
> >>
> >> "memcpy" only works for trivially copyable types to start with
> >> [basic.types.general] p2+p3.
> >> But it does work on indeterminate bytes, and faithfully copies those
> >> to the destination.
> >>
> >
> >We provide certain explicit guarantees only in the case of trivially
> >copyable types, but I do not think that it was intended that the result of
> >memcpying a non-trivially-copyable class object to a char buffer produces
> a
> >completely unspecified result. For example, consider
> >
> >#include <string.h>
> >
> >struct S {
> > char c;
> > S() = default;
> > S(const S&) {} // S is not trivially copyable
> >};
> >static_assert(sizeof(S) == 1);
> >
> >int main() {
> > S s{}; // value-initialization: s.c is zeroed
> > char buf[1];
> > memcpy(buf, &s, 1);
> > return buf[0];
> >}
> >
> >One might take the point of view that this program always returns 0, or
> >perhaps that because `S` is not trivially copyable you don't get such a
> >guarantee and instead some arbitrary value can be returned, but I don't
> >think we should interpret the standard such that `buf[0]` is given a
> >possibly-indeterminate value. That seems to defeat one of the purposes of
> >value-initialization.
> >
> >Furthermore, note that there are possibly-legitimate use cases for using a
> >non-trivially-copyable object as the source of `memcpy`. In Google Test,
> >when an `EXPECT_EQ` assertion fails, the framework prints out the two
> >values that were expected to be equal but were not. But if those values do
> >not provide a suitable `operator<<` enabling them to be printed, then the
> >framework instead prints out their object representations. My assumption
> as
> >a user is that, from C++26 onward, if I have not marked my local variables
> >`[[indeterminate]]`, then that operation will not cause UB.
>
> It could for EXPECT_EQ(vec[0], vec[1]) if there are uninitialized members
> involved, as objects with dynamic storage duration don't get erroneous
> value.
>
> Alternatively we could adopt something like P3530 and make EXPECT_EQ's
> fallback printer use that. As a bonus:
>
> 1. It will be defined even for EXPECT_EQ(vec[0], vec[1]), if there are
> uninitialized members.
> 2. You don't get the possible early termination caused by EB.
>
> And then it would also stop being a reason for disallowing
> [[indeterminate]] non-static data members.
>
> The example data structure presented in P3530 requires both an
> [[indeterminate]] data member and their proposed interface to be both
> efficient and correct.
>
> >
> >None of this, of course, prevents class authors from writing a constructor
> >that would deliberately overwrite the possibly-erroneous initial storage
> >bytes with indeterminate bytes, in which case my expectation as a user may
> >be subverted. What I think is that sharp knives should look sharp, marking
> >a class member `[[indeterminate]]` is a sharper knife than merely
> declaring
> >a local variable `[[indeterminate]]`, and therefore, the mechanism to
> force
> >a class member to have indeterminate value should look sharper than
> >`[[indeterminate]]` does. It should be the constructor explicitly
> >allocating an `[[indeterminate]]` local char buffer and then memcpying it
> >over whatever is already in the member. Hopefully a good compiler can
> >optimize this to remove the initial erroneous initialization.
> >
> >If we let people declare class members `[[indeterminate]]`, I am sure
> >Yongwei will use this feature responsibly but I am less confident about
> >everyone else. I think there will be some people who think "it is my
> >responsibility to make my class as fast as possible for my users, so I
> will
> >slap `[[indeterminate]]` on all the members" without thinking too hard
> >about the implications.
> >
> >
> >> For classes that have byte buffers or "scratch space", I would expect
> >> that the class author ensures they can't be copied, or, if the scratch
> >> space happens to contain an object, that object is copied as
> appropriate,
> >> instead of relying on the default copy constructor. Thus, the class
> >> isn't trivially copyable anyway, and thus "memcpy" doesn't apply.
> >>
> >> > When a user writes `[[indeterminate]]`, it is their intention to
> >> exploit the potential efficiency of not initializing certain storage.
> When
> >> I, as class author, write `[[indeterminate]]` on data members, it is my
> >> intention (and responsibility) to exploit the efficiency and make sure
> I do
> >> not make mistakes.
> >>
> >> [[indeterminate]] can't appear on data members at this time:
> >> [dcl.attr.indet] p1.
> >>
> >> Jens
> >>
> >>
> >> > If a reasonable use of my code requires users to write
> >> `[[indeterminate]]`, it is an overburden for users. If they are
> cautious,
> >> they will have to check the implementation details of my class, which
> >> defeats the purpose of encapsulation.
> >> >
> >> >
> >> > Furthermore, I have the concern that erroneous behaviour, as it is
> >> currently specified, may hinder the adoption of C++26, as it may slow
> down
> >> certain applications, */without a proper way of remedy/*. No, adding the
> >> [[indeterminate]] attribute to class-type local objects is not a remedy,
> >> but uglification. It is far too confusing to mark a class-type object
> with
> >> proper constructors as [[indeterminate]].
> >> >
> >> > I personally think it should be addressed in a new proposal, or maybe
> a
> >> revised P2795? Opinions?
> >> >
> >> > --
> >> > Yongwei Wu
> >> > URL: http://wyw.dcweb.cn/ <http://wyw.dcweb.cn/>
> >> >
> >>
> >> --
> >> Std-Discussion mailing list
> >> Std-Discussion_at_[hidden]
> >> https://lists.isocpp.org/mailman/listinfo.cgi/std-discussion
> >>
> >
> >
>

Received on 2025-03-15 22:55:26