Skip to content

CWG3219 [basic.types.trivial] Overeager no-op when no suitable value for value representation #980

Description

@hubert-reinterpretcast

Full name of submitter (unless configured in github; will be published with the issue): Hubert Tong

Reference (section label): [basic.types.trivial]

Link to reflector thread (if any): N/A

Issue description:

The wording says:

in which case the object’s value is replaced with an unspecified member of the corresponding subset that would result in the program having defined behavior, if any.

In other words, the object's value is unchanged/the object does not receive a value when no such value exists.

This runs into problems with the following:

  • The symbolic value model does not acknowledge any changes to the value from manipulation of the object representation except when an object acquires a value representation.
  • bit_cast has undefined behaviour when an object in the result does not receive a value.

For the first, consider:

#include <cstring>
int main(void) {
  int x = 42, *p = &x, *q = p + 1;
  std::memcpy(&p, &q,
              sizeof(p)); // giving `p` the value of `q` would _not_ lead to the
                          // program having defined behaviour
  return *p; // Is the value of `p` here still `&x`? Prior to P2434R5, no.
             // After: Maybe, but no UB if not (could be `&errno`)—seems
             // unimplementable in general.
}

For the second, consider:

#include <memory>
#include <cstdio>
int main(void) {
  int x = 42, *p = &x, *q = p + 1;
  p = std::bit_cast<int *>(q);  // UB here
  std::fprintf(stderr, "Hello!\n");  // observable checkpoint too late
  return *p;
}

Note that the status quo before P2434R5 already made the bit_cast case undefined behaviour when the value representation did not correspond to any value of the object's type.

Suggested resolution:

in which case the object’s value is replaced with an unspecified member of the corresponding subset, if any, that—if possible—would result in the program having defined behavior, if any. [Note: Lvalue-to-rvalue conversion has undefined behavior if the value representation of the object is not valid for the object's type.]

Activity

  1. safocl commented on Aug 17, 2026

    @safocl

    For the first, consider:

    #include <cstring>
    int main(void) {
      int x = 42, *p = &x, *q = p + 1;
      std::memcpy(&p, &q,
                  sizeof(p)); // giving `p` the value of `q` would _not_ lead to the
                              // program having defined behaviour
      return *p; // Is the value of `p` here still `&x`? Prior to P2434R5, no.
                 // After: Maybe, but no UB if not (could be `&errno`)—seems
                 // unimplementable in general.
    }

    this example contains UB -- the pointer p points past the end of an object and is indirected (expr.unary.op#1).

  2. languagelawyer commented on Aug 17, 2026

    @languagelawyer

    @safocl that's the point. Since giving past-the-end value would lead to UB, it is not given

  3. safocl commented on Aug 17, 2026

    @safocl

    @safocl that's the point. Since giving past-the-end value would lead to UB, it is not given

    Do you think this applies to the program as a whole? So the implementation should track where the pointer is used later?
    In my opinion, no. Why do you think this was done in the first place? (There's a very detailed proposal on this topic, where everything is explained — I haven't seen anything like your reasoning there — the reason, as described there, is completely logical and clear.)

  4. languagelawyer commented on Aug 17, 2026

    @languagelawyer

    In other words, the object's value is unchanged/the object does not receive a value when no such value exists.

    AFAIR, I've seen some well-known C++ community members interpreting sentences like «if Condition, then X happens» as «implicit UB» when the Condition is not true. However, do not remember anyone interpreting object replacement as UB if not transparently replaceable, only that name/pointer/reference won't rebind.

  5. safocl commented on Aug 17, 2026

    @safocl

    It seems that the idea suggested in the initial topic is based solely on an example similar to this:

    alignas(int) unsigned char a[sizeof(int)*3];
    auto p1 = new(a) int{42};
    auto p2 = new(a+sizeof(int)) int{24};
    
    auto p1_past_the_end = p1+1;

    Here the pointers p1_past_the_end and p2 may have the same value representation, but different values.

  6. safocl commented on Aug 17, 2026

    @safocl

    Note that the status quo before P2434R5 already made the bit_cast case undefined behaviour when the value representation did not correspond to any value of the object's type.

    but here the value of the pointer to int quite accommodates the value pointing past the end of an object

  7. hubert-reinterpretcast commented on Aug 17, 2026

    @hubert-reinterpretcast
    MemberAuthor

    but here the value of the pointer to int quite accommodates the value pointing past the end of an object

    The note is explaining one of the reasons why the proposed resolution does not provide some sort of replacement value in the case where the value representation did not correspond to any value of the object's type and, thereby, leaves such a case with UB from the bit_cast operation. That pre-existing UB in bit_cast is not strictly needed if the result is subject to a temporary materialization conversion.

  8. hubert-reinterpretcast commented on Aug 17, 2026

    @hubert-reinterpretcast
    MemberAuthor

    Here the pointers p1_past_the_end and p2 may have the same value representation, but different values.

    Your example (assuming that you adjust it to ensure that a has the necessary alignment) is more about https://wg21.link/p0593 than anything else in the discussion.

  9. safocl commented on Aug 17, 2026

    @safocl

    The note is explaining one of the reasons why the proposed resolution does not provide some sort of replacement value in the case where the value representation did not correspond to any value of the object's type and, thereby, leaves such a case with UB from the bit_cast operation. That pre-existing UB in bit_cast is not strictly needed if the result is subject to a temporary materialization conversion.

    but all values ​​of a pointer to int can be reproduced by a pointer to int

  10. safocl commented on Aug 17, 2026

    @safocl

    (assuming that you adjust it to ensure that a has the necessary alignment)

    Thanks, fixed.

  11. changed the title [-][basic.types.trivial] Overeager no-op when no suitable value for value representation[/-] [+]CWG3219 [basic.types.trivial] Overeager no-op when no suitable value for value representation[/+] on Aug 17, 2026
  12. jensmaurer commented on Aug 17, 2026

    @jensmaurer
    Member
  13. frederick-vs-ja commented on Aug 18, 2026

    @frederick-vs-ja

    The 2nd example seems also addressed in CWG3039.

  14. safocl commented on Aug 18, 2026

    @safocl

    (so everything was brief and seemed elegant, it all started with the introduction of the [basic.types.trivial] section)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions