On Thu, Mar 13, 2025 at 6:00 AM Jens Maurer via Std-Discussion <std-discussion@lists.isocpp.org> wrote:


On 13/03/2025 05.16, Yongwei Wu via Std-Discussion wrote:
> On Wed, 12 Mar 2025 at 10:46, Yongwei Wu <wuyongwei@gmail.com <mailto:wuyongwei@gmail.com>> wrote:
>
>     On Tue, 11 Mar 2025 at 23:11, Brian Bi <bbi5291@gmail.com <mailto:bbi5291@gmail.com>> 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.

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@lists.isocpp.org
https://lists.isocpp.org/mailman/listinfo.cgi/std-discussion


--
Brian Bi