---
title: "Addenda to the Undefined Behavior and IFNDR Annexes"
document: P4284R0
date: 2026-08-13
audience: CWG
reply-to:
  - "Joshua Berne <jberne4@bloomberg.net>"
---

Revision 0

* Original version of the paper

## 1

A few items have been brought up directly during the final merging of [P3596R3] into the C++ draft or since that merge completed:

* Shafik Yaghmour pointed out during the review ([P3596R3] PR [Comment](https://github.com/cplusplus/draft/pull/9107/#discussion_r3444880052)) that two cases of
undefined behavior introduced by contracts had been missed from [[class.base.init]](https://eel.is/c++draft/class.base.init).

To handle this we have added examples of both of these cases to [[class.base.init.mem.fun]](https://eel.is/c++draft/class.base.init.mem.fun).

* A. Jiang pointed out ([P3596R3] PR [Comment](https://github.com/cplusplus/draft/pull/9107#discussion_r3584329584)) that the example in [[ifndr:expr.prim.req.always.sub.fail]](https://eel.is/c++draft/ifndr:expr.prim.\protect \discretionary {\char \hyphenchar \font }{}{}req.\protect \discretionary {\char \hyphenchar \font }{}{}always.\protect \discretionary {\char \hyphenchar \font }{}{}sub.\protect \discretionary {\char \hyphenchar \font }{}{}fail)
 references a case where an earlier core issue ([CWG2392]) made this case no
longer IFNDR.

To address this, we have changed the example to use a different dependent expression that is always invalid (`new` `decltype((void)T{})`).

* A. Jiang pointed out ([P3596R3] PR [Comment](https://github.com/cplusplus/draft/pull/9107#discussion_r3584332046)) that the example in [[ifndr:basic.def.odr.definition.matches]](https://eel.is/c++draft/ifndr:basic.\protect \discretionary {\char \hyphenchar \font }{}{}def.\protect \discretionary {\char \hyphenchar \font }{}{}odr.\protect \discretionary {\char \hyphenchar \font }{}{}definition.\protect \discretionary {\char \hyphenchar \font }{}{}matches)
 is now well-formed thanks to [CWG2300].

To address this, we have changed the example to use a local function declaration that has a lambda for a default argument, which we then invoke.

* Thomas Köppe, during the merging of [P3596R3], suggested that many of the examples in
the annexes relied too heavily on putting expository information in comments in the code
examples. To improve the readability of the examples, we have adjusted a number of entries
to put more content in the prose around the example and less in the comments.

* Jan Schultke pointed out ([Proposed CWG issue](https://github.com/cplusplus/cwg/issues/922)) that the paper [P3899R3] made non-arithmetic
expressions such as pointer difference with arithmetic results that are not representable into
defined behavior (with no corresponding definition). This was a normative bug, but does mean
that the non-arithmetic results of pointer difference are a new category of UB distinct from
[[ub:expr.expr.eval]](https://eel.is/c++draft/ub:expr.expr.eval).

To address this we have foreshadowed the normative reinstatement of the conversion by making what was previously a note about conversion to `std::ptrdiff_t` into a normative statement and adding a corresponding ub entry in [expr.sub.pointers.representable].

* Jan Schultke noted that a number of cases were using `int`, `float`, or `double` and then showing
examples that were only relevant on platforms where those types had very specific properties
(such as being 32-bit). These examples have been updated to use various fixed-sized types from
`<cstdint>` and `<cstdfloat>`, such as `std::int32_t`, `std::float32_t`, and `std::float64_t`.

* A bug in [basic.def.odr.definition.matches] was noted by Lénárd Szolnoki (see [Editorial](https://github.com/cplusplus/draft/issues/9238) Issue
[9238](https://github.com/cplusplus/draft/issues/9238)), where the IFNDR case was being qualified to a definiable item that is *not* “an inline or
templated function”. The correct qualification is the opposite — the definitable item must be
inline or templated.

In addition, a number of papers landed in Brno, concurrently with [P3596R3], that added, removed, or modified the parts of the standard containing UB and IFNDR entries.

* CWG Motion 4 — [P3899R3] (Clarify the behavior of floating-point overflow) deletes the
blanket rule in [[expr.pre]](https://eel.is/c++draft/expr.pre) that made any result not mathematically defined or not representable
undefined, replacing it with a narrow rule scoped to arithmetic expressions — so an overflow
yielding a representable infinity or NaN is now well-defined, and division by zero becomes the
only arithmetic operation with undefined behavior.

To cover the change in this wording we have expanded the examples to include cases of UB for a `float` type that does not have NaN or infinities, all in [ub:expr.expr.eval].

* CWG Motion 7 — [P2434R5] (Nondeterministic pointer provenance) reworks pointer provenance
 in the core language. It adds a new undefined behavior in [[basic.compound]](https://eel.is/c++draft/basic.compound) — an
evaluation that produces, or gives an object, a pointer value to or past an object *O* and that
happens before the beginning of the storage duration for *O* — and it makes the integer-topointer
 conversion in [[expr.reinterpret.cast]](https://eel.is/c++draft/expr.reinterpret.cast) conditionally undefined when no representable
value would give the program defined behavior.

To address these changes, we added the following ub entries:

**–** [ub:basic.compound.pointer.before.storage.duration]

**–** [ub:expr.reinterpret.cast.invalid.pointer.value]

One change in [[cstdio.syn]](https://eel.is/c++draft/cstdio.syn) does not lead to a new annex entry because it is in the library, not the core wording.

* CWG Motion 8 — [P3347R6] (Invalid Pointer Operations) rewrites [[basic.compound]](https://eel.is/c++draft/basic.compound) paragraph
4 so that indirection through an invalid pointer remains undefined while most other operations
become implementation-defined or well-defined, and it deletes [[conv.lval]](https://eel.is/c++draft/conv.lval) paragraph 3.3. The
set of undefined behavior is essentially unchanged, but the wording must be considered for
impact on the annex entry.

This motion affected the wording around [ub:basic.compound.invalid.pointer]. This annex entry previously also covered freeing memory that has already been freed, but that is not the result of this particular wording so we have made the example slightly more specific.

* CWG Motion 10 — [P3950R1] (`return_value` & `return_void` Are Not Mutually Exclusive)
retains the undefined behavior of flowing off the end of a coroutine but rewords its trigger in
[[stmt.return.coroutine]](https://eel.is/c++draft/stmt.return.coroutine) in terms of whether overload resolution for `p.return_void()` succeeds.

The cases where [ub:stmt.return.coroutine.flow.off] might be triggered are changed very slightly because a promise type might have a `return_void` that is constrained and not usable. This does not seem to add to understanding the undefined behavior itself, which is more about general control flow in coroutines, and so we have not updated the wording for the annex entry.

* CWG Motion 14 — [P3424R2] (Deallocation Functions with Throwing Exception Specification
Are Ill-formed) makes it ill-formed to declare a deallocation function with a potentially-throwing
exception specification removing the possibility of such a function throwing.

[basic.stc.alloc.dealloc.throw] was removed entirely when this paper was merged.

* CWG Motion 17 — [P4101R1] (Consteval-only Values for C++26) replaces the type-based
model of consteval-only values with a value-based one. It adds a new IFNDR case in
[[expr.const.const]](https://eel.is/c++draft/expr.const.const) — an expression that odr-uses a variable declaring or referring to an
immediate object outside an immediate-function context, with a diagnostic required only
under a reachability condition.

To address this paper, we added the following ifndr entries:

**–** [ifndr:expr.const.const.immediate.var.def].

* CWG Motion 18 — [P2414R12] (Pointer lifetime-end zap proposed solutions) adds a new
undefined behavior in [[conv.lval]](https://eel.is/c++draft/conv.lval) for an lvalue-to-rvalue conversion when the bits of the value
representation are not valid for the object’s type, and it removes the undefined behavior
previously reachable through volatile reads and volatile assignments of a lifetime-end pointer
value, which now yield a well-defined prospective pointer value.

Though this paper changes a paragraph that defines some undefined behavior, the UB itself is not changed.

All of the above changes are represented in the wording changes below.

## 2

These wording changes are relative to the C++29 draft sources with git hash [ec1038c8](https://github.com/cplusplus/draft/tree/ec1038c811ab4c5ac277e93f41660c38f2dc7a1b), last modified on Sat, 1 Aug 2026 19:40:39 +0200.

**Modified** **Section** **Contents**

**6** **Basics** **[basic]** **5** 6.9 Types [basic.types] 5 6.9.5 Compound types [basic.compound] 5

**7** **Expressions** **[expr]** **6** 7.6 Compound expressions [expr.compound] 6 7.6.1 Postfix expressions [expr.post] 6 7.6.1.10 Reinterpret cast [expr.reinterpret.cast] 6 7.6.6 Additive operators [expr.add] 6 7.7 Constant evaluation [expr.const] 6 7.7.3 Constant expressions [expr.const.const] 6

**F** **Core** **undefined** **behavior** **[ub]** **7** F.2 Clause 6[basic]: Basics [ub.basic] 7

:::wording-add

<ins>F.2.16+a basic.compound.pointer.before.storage.duration</ins>

:::

:::wording-add

<ins>[ub:basic.compound.pointer.before.storage.duration]</ins> 7 F.2.17 basic.compound.invalid.pointer [ub:basic.compound.invalid.pointer] 8 F.2.19 intro.races.data [ub:intro.races.data] 8 F.2.20 intro.progress.stops [ub:intro.progress.stops] 8 F.2.21 basic.start.main.exit.during.destruction [ub:basic.start.main.exit.during.destruction] 9 F.2.22 basic.start.term.use.after.destruction [ub:basic.start.term.use.after.destruction] 9 F.3 Clause 7[expr]: Expressions [ub.expr] 10 F.3.1 expr.expr.eval [ub:expr.expr.eval] 10 F.3.6 conv.double.out.of.range [ub:conv.double.out.of.range] 10 F.3.7 conv.fpint.float.not.represented [ub:conv.fpint.float.not.represented] 11 <ins>F.3.19+a expr.reinterpret.cast.invalid.pointer.value</ins>

:::

:::wording-add

<ins>[ub:expr.reinterpret.cast.invalid.pointer.value]</ins> 11 F.3.28 expr.mul.div.by.zero [ub:expr.mul.div.by.zero] 11 F.3.29 expr.mul.representable.type.result [ub:expr.mul.representable.type.result] 12 F.3.30 expr.add.out.of.bounds [ub:expr.add.out.of.bounds] 12 <ins>F.3.30+a expr.sub.pointers.representable</ins> <ins>[ub:expr.sub.pointers.representable]</ins> 13 F.3.33 expr.shift.neg.and.width [ub:expr.shift.neg.and.width] 13 F.6 Clause 11[class]: Classes [ub.class] 13 F.6.3 class.base.init.mem.fun [ub:class.base.init.mem.fun] 13 F.6.6 class.cdtor.convert.pointer [ub:class.cdtor.convert.pointer] 14

:::

:::wording-add

G Ill-formed, no diagnostic required [ifndr] 15 G.3 Clause 6[basic]: Basics [ifndr.basic] 15 G.3.5 basic.def.odr.definition.matches [ifndr:basic.def.odr.definition.matches] 15 G.4 Clause 7[expr]: Expressions [ifndr.expr] 15 G.4.1 expr.prim.req.always.sub.fail [ifndr:expr.prim.req.always.sub.fail] 16 <ins>G.4.1+a expr.const.const.immediate.var.def</ins>

:::

:::wording-add

<ins>[ifndr:expr.const.const.immediate.var.def]</ins> 16

:::

**Modifications**

| 6 Basics | [basic] |
| --- | --- |
| 6*.*9 **Types** | **[basic.types]** |
| 6*.*9*.*5 **Compound** **types** | **[basic.compound]** |

Modify section 6.9.5[basic.compound], paragraph 5:

:::wording-add

5 If an evaluation produces or causes an object to have (6.9.2[basic.types.trivial]) a pointer value to or past the end of an object O and happens before the beginning of the duration of the region of storage for O, the behavior is undefined (F.2.16+a[ubx:basic.compound. <ins>pointer.before.storage.duration])</ins>.

:::

[*Note* *5*: Relaxed atomic operations (32.5.4[atomics.order]) can produce such values. Conversions from integers avoid producing them (7.6.1.10[expr.reinterpret.cast]). *— end* *note*]

| 7 Expressions | [expr] |
| --- | --- |
| 7*.*6 **Compound** **expressions** | **[expr.compound]** |
| 7*.*6*.*1 **Postfix** **expressions** | **[expr.post]** |
| 7*.*6*.*1*.*10 **Reinterpret** **cast** | **[expr.reinterpret.cast]** |

Modify section 7.6.1.10[expr.reinterpret.cast], paragraph 5:

:::wording-add

5 A value of integral type or enumeration type can be explicitly converted to a pointer. If the value is one that can be produced by converting one or more pointer values (6.9.5[basic. compound]) to an integral type, the result is an unspecified choice among all such values that would result in the program having defined behavior. If no such value exists, the behavior is undefined <ins>(F.3.19+a[ubx:expr.reinterpret.cast.invalid.pointer.value])</ins>.

:::

[*Note* *4*: It is possible for the result to be an invalid pointer value or to not be valid in the context of the conversion (6.9.5[basic.compound]) because it points to an object in a region of storage whose duration has ended or has not yet begun. *— end* *note*]

Otherwise, the result is implementation-defined.

[*Note* *5*: It can be an invalid pointer value. *— end* *note*]

### 7*.*6*.*6 Additive operators

Modify section 7.6.6[expr.add], paragraph 5:

5 The result of subtracting two pointer expressions `P` and `Q` is a prvalue of type `std::ptrdiff_t` (17.2.4[support.types.layout]).

— (5.1) If `P` and `Q` both evaluate to null pointer values, the value is 0.

:::wording

— (5.2) Otherwise, if P and Q point to, respectively, array elements i and j of the same array object x, the expression P - Q has the value i −j. [Note 2: If the value i −j is not in the range of representable values of type std::ptrdiff_t, the behavior is undefined <del>(7.1[expr.pre])</del> <ins>(F.3.30+a[ubx:expr.sub.pointers.representable])</ins>. <del>— end note]</del>

:::

— (5.3) Otherwise, the behavior is undefined (F.3.31[ubx:expr.add.sub.diff.pointers]).

| 7*.*7 Constant evaluation | [expr.const] |
| --- | --- |
| 7*.*7*.*3 **Constant** **expressions** | **[expr.const.const]** |

Modify section 7.7.3[expr.const.const], paragraph 3:

3 Every immediate object shall be

— (3.1) the object associated with a constexpr variable or a subobject thereof,

— (3.2) a template parameter object (13.2[temp.param]) or a subject thereof, or

— (3.3) an object whose lifetime begins and ends during the evaluation of a core constant expression.

[*Example* *1*:

```cpp
consteval int plus1(int x) { return x + 1; }
template <auto V> struct C {};
auto a = plus1;
                                // error: immediate object not associated with constexpr variable
constexpr auto b = plus1;
                                // OK
auto c = C<plus1>();
                                // OK
auto d = ^^int;
                                // error: immediate object not associated with constexpr variable
auto e = C<^^char>();
                                // OK
```

*— end* *example*]

Each expression *E* that odr-uses a variable that declares or refers to an immediate object shall be in an immediate function context; a diagnostic is required only if either

— (3.1) the innermost declaration that contains *E* or

:::wording-add

— (3.2) the defining declaration of the variable is reachable from the other <ins>(G.4.1+a[ifndrx:expr.const.const.immediate.var.def])</ins>.

:::

| *F* Core undefined behavior | [ub] |
| --- | --- |
| *F.*2 **Clause** **6[basic]:** **Basics** | **[ub.basic]** |
| *F.*2*.16* + *a* **basic.compound.pointer.before.storage.duration** | **[ub:basic.compound.pointer.before.storage.duration]** |

:::wording-add

Add a new UB description <ins>F.2.16+a</ins>[ub:basic.compound.pointer.before.storage.duration] after F.2.16[ub:basic.stc.alloc.zero.dereference]

:::

:::wording-add

<ins>F.2.16+a</ins> <ins>[ub:basic.compound.pointer.before.storage.duration]</ins> <ins>Specified in: 6.9.5[ubx:basic.compound.pointer.before.storage.duration]</ins>

:::

:::wording-add

<ins>1</ins> <ins>Producing a pointer value to or past the end of an object before the beginning of the</ins> <ins>duration of that object’s storage has undefined behavior. Therefore any pointer created</ins> <ins>before the storage duration of an object cannot be a pointer to that object.</ins>

:::

:::wording-add

<ins>2</ins> <ins>[Example 1: In the following example, initializing the pointer p has undefined behavior because</ins> <ins>all possible values of p, including &y, lead to undefined behavior.</ins>

:::

:::wording-add

<ins>#include <cstdint></ins> <ins>void f() {</ins> <ins>int x;</ins> <ins>std::uintptr_t pval = reinterpret_cast<std::uintptr_t>((&x)+1);</ins> <ins>int *p = reinterpret_cast<int*>(pval);</ins> <ins>{</ins> <ins>int y;</ins> <ins>// storage duration of y begins, so p is not &y</ins> <ins>if (pval == reinterpret_cast<std::uintptr_t>(&y)) { // consider when x and y are consecutive</ins> <ins>*p = 17;</ins> <ins>// undefined behavior, so p is not (&x)+1</ins> <ins>}</ins> <ins>}</ins> <ins>}</ins>

:::

:::wording-add

<ins>— end example]</ins>

:::

*F.*2*.*17 **basic.compound.invalid.pointer** **[ub:basic.compound.invalid.pointer]**

Modify UB description F.2.17[ub:basic.compound.invalid.pointer], paragraphs 1-2:

**F.2.17** **[ub:basic.compound.invalid.pointer]** **Specified** **in:** 6.9.5[ubx:basic.compound.invalid.pointer] Indirection or the invocation of a deallocation function with a pointer value referencing storage that has been freed has undefined behavior. (Most other uses of such a pointer have implementation-defined behavior.)

2 [*Example* *1*:

:::wording-remove

void f() { int *x = new int{5}; delete x; int y = *x; // undefined behavior <del>delete x;</del> <del>// undefined behavior←�</del> }

:::

*— end* *example*]

*F.*2*.*19 **intro.races.data** **[ub:intro.races.data]**

Modify UB description F.2.19[ub:intro.races.data], paragraph 2:

**F.2.19** **[ub:intro.races.data]** **Specified** **in:** 6.10.2.2[ubx:intro.races.data]

:::wording-add

2 [Example 1: <ins>In the following example, t1, t2 and t3 have a data race on access of variable count.</ins>

:::

`int` `count` `=` `0;` `auto` `f` `=` `[&]` `{` `count++;` `};` `std::thread` `t1{f},` `t2{f},` `t3{f};`*←**�*

:::wording-remove

// undefined behavior <del>t1, t2 and t3 have a data race on access of variable count</del>

:::

*— end* *example*]

*F.*2*.*20 **intro.progress.stops** **[ub:intro.progress.stops]**

Modify UB description F.2.20[ub:intro.progress.stops], paragraph 2:

:::wording-remove

F.2.20 [ub:intro.progress.stops] Specified in: 6.10.2.3[ubx:intro.progress.stops] <del>[Example 1:</del>

:::

:::wording-add

2 <ins>[Example a: In the following example, the loop condition !stop() is not a constant expression,</ins> <ins>so the infinite loop which results is undefined behavior.</ins>

:::

:::wording

bool stop() { return false; } void busy_wait_thread() { while (!stop()); // undefined behavior<del>, thread makes no progress but the loop</del> <del>}</del> <del>// is not trivial because stop() is not a constant expression←�</del> <ins>←�</ins> <ins>}←�</ins> int main() { std::thread t(busy_wait_thread); t.join(); }

:::

*— end* *example*]

*F.*2*.*21 **basic.start.main.exit.during.destruction** **[ub:basic.start.main.exit.during.destruction]**

Modify UB description F.2.21[ub:basic.start.main.exit.during.destruction], paragraph 2:

**F.2.21** **[ub:basic.start.main.exit.during.destruction]** **Specified** **in:** 6.10.3.1[ubx:basic.start.main.exit.during.destruction]

2 [*Example* *1*:

```cpp
#include <cstdlib>
struct Exiter {
  ~Exiter() { std::exit(0); }
};
```

:::wording-remove

Exiter ex; int main() {} <del>// undefined behavior when destructor of static variable ex is called it will call std::exit←�</del>

:::

:::wording-remove

<del>— end example]</del>

:::

:::wording-add

<ins>During the destruction of static variables after main completes, the destructor of the variable ex,</ins> <ins>which invokes std::exit, has undefined behavior. — end example]</ins>

:::

*F.*2*.*22 **basic.start.term.use.after.destruction** **[ub:basic.start.term.use.after.destruction]**

Modify UB description F.2.22[ub:basic.start.term.use.after.destruction], paragraph 2:

**F.2.22** **[ub:basic.start.term.use.after.destruction]** **Specified** **in:** 6.10.3.4[ubx:basic.start.term.use.after.destruction]

2 [*Example* *1*:

```cpp
struct A {};
void f() {
  static A a;
}
struct B {
  B() { f(); }
};
struct C {
  ~C() { f(); }
};
```

:::wording-remove

C c; B b; // call to f() in constructor begins lifetime of a int main() {}←� <del>// undefined behavior, static objects are destructed in reverse order, in this case a then b and</del> <del>// finally c. When the destructor of c is called, it calls f() which passes through the definition of</del> <del>// previously destroyed block-scope object←�</del>

:::

:::wording-add

<ins>// undefined behavior←�</ins>

:::

:::wording-remove

<del>— end example]</del>

:::

:::wording-add

<ins>Objects with static storage duration are destroyed in reverse order of the completion of their</ins> <ins>construction. In the above example, first c then a and finally b completes construction. When</ins> <ins>the destructor of c is called after first destroying b and then a, it calls f() which passes through</ins> <ins>the definition of the previously destroyed block-scope object a. — end example]</ins>

:::

| *F.*3 Clause 7[expr]: Expressions | [ub.expr] |
| --- | --- |
| *F.*3*.*1 **expr.expr.eval** | **[ub:expr.expr.eval]** |

Modify UB description F.3.1[ub:expr.expr.eval], paragraph 2:

**F.3.1** **[ub:expr.expr.eval]** **Specified** **in:** 7.1[ubx:expr.expr.eval]

2 [*Example* *1*:

:::wording-add

<ins>#include <cstdint>←�</ins> #include <limits> int main() {

:::

:::wording-remove

<del>// Assuming 32-bit int, the range of values is: −2, 147, 483, 648 to 2, 147, 483, 647.</del> <del>int x1 = std::numeric_limits<int>::max() + 1;</del>

:::

:::wording-remove

<del>// undefined behavior, 2, 147, 483, 647 + 1 is not representable as an int</del> <del>int x2 = std::numeric_limits<int>::min() / -1;</del>

:::

:::wording

<del>// undefined behavior, −2, 147, 483, 648/ −1 is not representable as an int←�</del> <ins>std::int32_t x1 = std::numeric_limits<std::int32_t>::max() + 1;</ins>

:::

:::wording-add

<ins>// undefined behavior, 2, 147, 483, 647 + 1 is not representable as a std::int32_t</ins> <ins>std::int32_t x2 = std::numeric_limits<std::int32_t>::min() / -1;</ins>

:::

:::wording-add

<ins>// undefined behavior, −2, 147, 483, 648/ −1 is not representable as a std::int32_t</ins> <ins>}</ins>

:::

:::wording-add

<ins>— end example]</ins>

:::

:::wording-add

<ins>[Example 1+a: Assuming a float type that does not have NaN or infinities, the following</ins> <ins>operations have undefined behavior because their results are not in the range of float.</ins>

:::

:::wording-add

<ins>#include <limits></ins> <ins>int main() {</ins> <ins>float x1 = std::numeric_limits<float>::max() * 2; // undefined behavior, infinite value</ins> <ins>float x2 = 0.0f / 0.0f;</ins> <ins>// undefined behavior, NaN←�</ins> }

:::

*— end* *example*]

*F.*3*.*6 **conv.double.out.of.range** **[ub:conv.double.out.of.range]**

Modify UB description F.3.6[ub:conv.double.out.of.range], paragraph 2:

:::wording-remove

F.3.6 [ub:conv.double.out.of.range] Specified in: 7.3.10[ubx:conv.double.out.of.range] <del>[Example 1:</del>

:::

:::wording-add

2 <ins>[Example a: Assume a 32-bit int, 32-bit float, and a 64-bit double for the following example.</ins>

:::

```cpp
#include <limits>
int main() {
```

:::wording-remove

<del>// Assuming 32-bit int, 32-bit float and 64-bit double.←�</del> double d2 = std::numeric_limits<double>::max(); float f = d2; // undefined behavior on systems where the range of representable values // of float is [-max,+max]; on systems where the range of representable // values is [-inf,+inf] this would not be undefined behavior int i = d2; // undefined behavior, the max value of double is not representable as int }

:::

*— end* *example*]

*F.*3*.*7 **conv.fpint.float.not.represented** **[ub:conv.fpint.float.not.represented]**

Modify UB description F.3.7[ub:conv.fpint.float.not.represented], paragraph 2:

**F.3.7** **[ub:conv.fpint.float.not.represented]** **Specified** **in:** 7.3.11[ubx:conv.fpint.float.not.represented]

2 [*Example* *1*:

:::wording-add

#include <cstdint> <ins>#include <stdfloat></ins> <ins>#include <</ins>`limits>` int main() {

:::

:::wording

<del>// Assuming 32-bit int, the range of values is: −2, 147, 483, 648 to</del> <del>// 2, 147, 483, 647. Assuming 32-bit float and 64-bit double.</del> <del>double</del><ins>std::float64_t</ins> `d`<ins>␣</ins>`=` <ins>static_cast<std::float64_t></ins>`(`<del>double)</del>`std::numeric_limits<`<del>int</del><ins>std::int32_t</ins>`>::max()`<ins>)</ins> `+` `1;` <del>int</del><ins>std::int32_t</ins> x1 = d;␣ // undefined behavior, 2, 147, 483, 647 + 1 is not representable as intstd::int32_t }

:::

*— end* *example*]

*F.*3*.19* + *a* **expr.reinterpret.cast.invalid.pointer.value** **[ub:expr.reinterpret.cast.invalid.pointer.value]**

:::wording-add

Add a new UB description <ins>F.3.19+a</ins>[ub:expr.reinterpret.cast.invalid.pointer.value] after F.3.19[ub:expr.static.cast.does.not.contain.original.member]

:::

:::wording-add

<ins>F.3.19+a</ins> <ins>[ub:expr.reinterpret.cast.invalid.pointer.value]</ins> <ins>Specified in: 7.6.1.10[ubx:expr.reinterpret.cast.invalid.pointer.value]</ins>

:::

:::wording-add

<ins>1</ins> <ins>Converting a value of integral or enumeration type to a pointer using reinterpret_cast</ins> <ins>where there is no pointer that would convert back to that integral or enumeration value</ins> <ins>has undefined behavior.</ins>

:::

:::wording-add

<ins>2</ins> <ins>[Example 1: In the following example, undefined behavior will occur when converting pval to a</ins> <ins>pointer. The results based on x or y would lead to undefined behavior, as would any result based</ins> <ins>on other objects (that share storage with y) that are outside of lifetime.</ins>

:::

:::wording-add

<ins>#include <cstdint></ins> <ins>void f()</ins> <ins>{</ins> <ins>int x,y;</ins> <ins>// can be at consecutive addresses in memory</ins> <ins>int *p = (&x)+1;</ins> <ins>// pointer past the end of x</ins> <ins>std::uintptr_t pval = reinterpret_cast<std::uintptr_t>(p);</ins> <ins>if (pval == reinterpret_cast<std::uintptr_t>(&y)) {</ins> <ins>// consider when x and y are consecutive</ins> <ins>int *q = reinterpret_cast<int*>(pval);</ins> <ins>// undefined behavior</ins> <ins>q-1;</ins> <ins>// undefined behavior if q is &y</ins> <ins>*q;</ins> <ins>// undefined behavior if q is (&x)+1 or refers to some int outside f</ins> <ins>}</ins> <ins>}</ins>

:::

:::wording-add

<ins>— end example]</ins>

:::

*F.*3*.*28 **expr.mul.div.by.zero** **[ub:expr.mul.div.by.zero]**

Modify UB description F.3.28[ub:expr.mul.div.by.zero], paragraph 2:

**F.3.28** **[ub:expr.mul.div.by.zero]** **Specified** **in:** 7.6.5[ubx:expr.mul.div.by.zero]

2 [*Example* *1*:

```cpp
int main() {
  int x = 1 / 0;
                        // undefined behavior, division by zero
  double d = 1.0 / 0.0; // undefined behavior on systems where the range of
```

:::wording-remove

<del>// representable values of double is [-max,+max], on systems where</del> <del>// representable values is [-inf,+inf] this would not be undefined behavior←�</del> }

:::

*— end* *example*]

*F.*3*.*29 **expr.mul.representable.type.result** **[ub:expr.mul.representable.type.result]**

Modify UB description F.3.29[ub:expr.mul.representable.type.result], paragraph 2:

**F.3.29** **[ub:expr.mul.representable.type.result]** **Specified** **in:** 7.6.5[ubx:expr.mul.representable.type.result]

:::wording-add

2 [Example 1: <ins>Dividing the minimum std::int32_t value of −2, 147, 483, 648 by −1 yields</ins> <ins>2, 147, 483, 648 which is not representable by std::int32_t on such platforms.</ins>

:::

:::wording-add

#include <cstdint> <ins>#include <</ins>`limits>` int main() { intstd::int32_t x = std::numeric_limits<intstd::int32_t>::min() / -1;←�

:::

:::wording

// <del>Assuming LP64 −2, 147, 483, 648 which when divided by −1</del><ins>undefined behavior (F.3.1[ub:expr.expr.eval])</ins> <ins>std::int32_t y = std::numeric_limits<std::int32_t>::min() % -1;</ins> // <del>gives us 2, 147, 483, 648 which is not</del> <del>representable by int.</del><ins>undefined behavior</ins> }

:::

*— end* *example*]

*F.*3*.*30 **expr.add.out.of.bounds** **[ub:expr.add.out.of.bounds]**

Modify UB description F.3.30[ub:expr.add.out.of.bounds], paragraph 2:

**F.3.30** **[ub:expr.add.out.of.bounds]** **Specified** **in:** 7.6.6[ubx:expr.add.out.of.bounds]

2 [*Example* *1*:

:::wording-remove

<del>static const int arrs[10]{};</del> <del>int main() {</del> <del>const int *y = arrs + 11;</del> <del>// undefined behavior, creating an out of bounds pointer</del> <del>}</del>

:::

:::wording-remove

<del>— end example]</del>

:::

:::wording-remove

<del>[Example 2:</del>

:::

:::wording

<ins>static const int arr[10]{};←�</ins> static const int arrs[10][10]{}; int main() { `const` `int`<del>(*y)[10] = arrs</del> <ins>*x = arr</ins> `+` `11;`<ins>␣␣</ins> // undefined behavior, creating an out of bounds pointer <ins>const int(*y)[10] = arrs + 11;</ins> <ins>// undefined behavior, creating an out of bounds pointer←�</ins> }

:::

*— end* *example*]

*F.*3*.30* + *a* **expr.sub.pointers.representable** **[ub:expr.sub.pointers.representable]**

:::wording-add

Add a new UB description <ins>F.3.30+a</ins>[ub:expr.sub.pointers.representable] after F.3.30[ub:expr.add. out.of.bounds]

:::

:::wording-add

<ins>F.3.30+a</ins> <ins>[ub:expr.sub.pointers.representable]</ins> <ins>Specified in: 7.6.6[ubx:expr.sub.pointers.representable]</ins>

:::

:::wording-add

<ins>1</ins> <ins>Subtracting pointers when the result is too large to represent in std::ptrdiff_t has</ins> <ins>undefined behavior.</ins>

:::

:::wording-add

<ins>2</ins> <ins>[Example 1: Note that this example requires being able to create a single object whose size is</ins> <ins>larger than the maximum value representable by std::ptrdiff_t, in order to get two pointers</ins> <ins>whose difference can be computed, and platforms that allow this are rare (6.8.2[intro.object],</ins> <ins>Annex B[implimits]).</ins>

:::

:::wording-add

<ins>#include <limits></ins> <ins>#include <iterator></ins> <ins>char big[static_cast<std::size_t>(std::numeric_limits<std::ptrdiff_t>::max())+1];</ins> <ins>std::ptrdiff_t d = std::end(big) - std::begin(big);</ins> <ins>// undefined behavior</ins>

:::

:::wording-add

<ins>— end example]</ins>

:::

*F.*3*.*33 **expr.shift.neg.and.width** **[ub:expr.shift.neg.and.width]**

Modify UB description F.3.33[ub:expr.shift.neg.and.width], paragraph 2:

**F.3.33** **[ub:expr.shift.neg.and.width]** **Specified** **in:** 7.6.7[ubx:expr.shift.neg.and.width]

:::wording-add

2 [Example 1: <ins>In the following example, assume that std::uint32_t is supported (17.4.1[cstdint.</ins> <ins>syn]).</ins>

:::

:::wording

<del>int</del><ins>#include <cstdint></ins> <ins>std::uint32_t</ins> `y` `=` <ins>std::uint32_t(</ins>`1`<ins>)</ins> `<<` `-1;`<del>␣␣␣␣␣</del> // undefined behavior, shift is negative <del>←�</del> <del>static_assert(sizeof(int) == 4 && CHAR_BIT == 8);</del> <del>int</del><ins>std::uint32_t</ins> `y1` `=` <ins>std::uint32_t(</ins>`1`<ins>)</ins> `<<` `32;`<del>␣␣␣␣␣</del> // undefined behavior, shift is equal to the bit width of int <del>int</del><ins>std::uint32_t</ins> `y2` `=` <ins>std::uint32_t(</ins>`1`<ins>)</ins> `>>` `32;`<del>␣␣␣␣␣</del> // undefined behavior, shift is equal to the bit width of int

:::

*— end* *example*]

| *F.*6 Clause 11[class]: Classes | [ub.class] |
| --- | --- |
| *F.*6*.*3 **class.base.init.mem.fun** | **[ub:class.base.init.mem.fun]** |

Modify UB description F.6.3[ub:class.base.init.mem.fun], paragraphs 1-2:

:::wording-add

F.6.3 [ub:class.base.init.mem.fun] Specified in: 11.9.3[ubx:class.base.init.mem.fun] Calling a member function before all the mem-initializers for base classes have completed, or <ins>after base classes have been destroyed,</ins> has undefined behavior. <ins>This includes precondition</ins> <ins>assertions of the constructor and postcondition assertions of the destructor.</ins>

:::

2 [*Example* *1*:

:::wording-add

class A { public: A(int); }; class B : public A { int j; public: int f(); B() <ins>pre( this->f() )</ins> <ins>// undefined behavior, calls member function in constructor precondition←�</ins> : A(f()), // undefined behavior, calls member function but base A not yet initialized j(f()) {} // defined, bases are all initialized <ins>←�</ins>

:::

:::wording-add

<ins>B(int x)</ins> <ins>pre( this->f() )</ins> <ins>// undefined behavior, calls member function in delegating constructor precondition</ins> <ins>: B() {}</ins>

:::

:::wording-add

<ins>~B()</ins> <ins>post( this->f() ) // undefined behavior, calls member function in destructor postcondition</ins> <ins>{}←�</ins> }; class C { public: C(int); }; class D : public B, C { int i; public: D() : C(f()), // undefined behavior, calls member function but base C not yet initialized i(f()) {} // defined, bases are all initialized };

:::

*— end* *example*]

*F.*6*.*6 **class.cdtor.convert.pointer** **[ub:class.cdtor.convert.pointer]**

Modify UB description F.6.6[ub:class.cdtor.convert.pointer], paragraph 2:

**F.6.6** **[ub:class.cdtor.convert.pointer]** **Specified** **in:** 11.9.5[ubx:class.cdtor.convert.pointer]

2 [*Example* *1*:

:::wording-remove

struct A { }; struct B : virtual A { }; struct C : B { }; struct D : virtual A { D(A*); }; struct X { X(A*); }; struct E : C, D, X { E() : D(this), // undefined behavior<del>, upcast from E* to A* might use path E* →D* →A*</del> <del>// but D is not constructed</del>

:::

:::wording

<del>// ‘‘D((C*)this)’’ would be defined, E* →C* is defined because E() has started,</del> <del>// and C* →A* is defined because C is fully constructed←�</del> <del>←�</del> <ins>←�</ins>

:::

```cpp
  X(this) {}
                    // defined, upon construction of X, C/B/D/A sublattice is fully constructed
};
```

:::wording

<del>— end example]</del> <ins>In the above constructor for E, upcast from E* to A* might use the path E* →D* →A*, but the</ins> <ins>D subobject has not yet been constructed. If the member initializer was instead “D((C*)this)”</ins> <ins>then that would be defined, because E* →C* is defined (E() has started) and C* →A* is defined</ins> <ins>(the C subobject is fully constructed). — end example]</ins> Modify IFNDR description G.3.5[ifndr:basic.def.odr.definition.matches], paragraphs 1-2: G.3.5 [ifndr:basic.def.odr.definition.matches] Specified in: 6.3[ifndrx:basic.def.odr.definition.matches] If there are definitions in different translation units of a definable item D where — (1.1) D is not defined by an injected declaration (7.7.6[expr.const.reflect]), — (1.2) D is not an inline or templated function or variable, and — (1.3) D is not attached to a named module or the declarations are not reachable from one another, that do not satisfy the matching rules described in 6.3[basic.def.odr], the program is ill-formed, no diagnostic required. 2 [Example 1: Translation unit #1: inline void f() {} // #1 inline void g() {} // #2 inline void hf() {[]{}();} // #31 <ins>inline void g() {}</ins> <ins>// #2</ins> <ins>inline void h(bool cond, void (*p)() = []{}) {</ins> <ins>if (cond) h(false);</ins> <ins>}</ins> <ins>// #3←�</ins> namespace { int i = 0; } `inline` `void` `j()` `{++i;}`<ins>␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣</ins> // #4 Translation unit #2: inline void f() {} // OK, same as #1 inline void g() {;} // IFNDR, different sequence of tokens than #2 `inline` `void` `h(`<ins>bool cond, void (*p</ins>`)`<ins>()</ins> <del>{</del><ins>=</ins> `[]{}`<del>(</del>`)`<del>;}</del> <del>// IFNDR, closure has different type than #3</del><ins>{</ins> <ins>if (cond) h(false);</ins> <ins>}</ins> <ins>// IFNDR, closure in default argument has different type than #3←�</ins> namespace { int i = 0; } inline void j() {++i; } // IFNDR, i refers to different entity than in #4 — end example]

:::

| *G* Ill-formed, no diagnostic required | [ifndr] |
| --- | --- |
| *G.*3 **Clause** **6[basic]:** **Basics** | **[ifndr.basic]** |
| *G.*3*.*5 **basic.def.odr.definition.matches** | **[ifndr:basic.def.odr.definition.matches]** |

| *G.*4 Clause 7[expr]: Expressions | [ifndr.expr] |
| --- | --- |
| *G.*4*.*1 **expr.prim.req.always.sub.fail** | **[ifndr:expr.prim.req.always.sub.fail]** |

Modify IFNDR description G.4.1[ifndr:expr.prim.req.always.sub.fail], paragraph 2:

**G.4.1** **[ifndr:expr.prim.req.always.sub.fail]** **Specified** **in:** 7.5.8.1[ifndrx:expr.prim.req.always.sub.fail]

2 [*Example* *1*:

:::wording

template <typename T> concept C = requires { `new` <del>int[-</del><ins>decltype</ins>`(`<del>int)sizeof</del>`(`<ins>void)</ins>`T`<ins>{}</ins>`)`<del>]</del>`;`<del>␣␣␣</del> // IFNDR, <del>the size</del><ins>invalid use</ins> of <del>the allocation is required to be</del> <del>greater</del>

:::

:::wording

<del>// than zero but can never be</del><ins>void in all instantiations</ins> };

:::

*— end* *example*]

*G.*4*.1* + *a* **expr.const.const.immediate.var.def** **[ifndr:expr.const.const.immediate.var.def]**

:::wording-add

Add a new IFNDR description <ins>G.4.1+a</ins>[ifndr:expr.const.const.immediate.var.def] after G.4.1[ifndr:expr.prim.req.always.sub.fail]

:::

:::wording-add

<ins>G.4.1+a</ins> <ins>[ifndr:expr.const.const.immediate.var.def]</ins> <ins>Specified in: 7.7.3[ifndrx:expr.const.const.immediate.var.def]</ins>

:::

:::wording-add

<ins>1</ins> <ins>A variable that declares or refers to an immediate object must be odr-used only from</ins> <ins>immediate function contexts. No diagnostic is required if the odr-use cannot reach the</ins> <ins>defining declaration of the variable or the defining declaration cannot reach the odr-use.</ins>

:::

:::wording-add

<ins>2</ins> <ins>[Example 1:</ins>

:::

:::wording-add

<ins>Translation unit #1:</ins>

:::

:::wording-add

<ins>struct S { decltype(^^::) d = ^^::; };</ins> <ins>constexpr S s;</ins> <ins>// #1</ins>

:::

:::wording-add

<ins>Translation unit #2:</ins>

:::

:::wording-add

<ins>struct S;</ins> <ins>extern const S s;</ins> <ins>const void* p = &s;</ins> <ins>// IFNDR, cannot reach defining declaration that is immediate object</ins>

:::

:::wording-add

<ins>— end example]</ins>

:::

### 3

These changes are being published for the record, and, given the editorial nature of the annex, may be simply merged as editorial issues. Our intent with this paper is to be a companion piece for [P3596R3] to reference the initial contents of the new annexes.

## Acknowledgments

Thank you to Jens Maurer, Thomas Köppe, Shafik Yaghmour, A. Jiang, Dan Katz, Davis Herring, Timur Doumler, Jan Schultke, and Lénárd Szolnoki, for contributing to, reviewing, authoring, or co-authoring these updates.

Claude (Anthropic) was used for editorial assistance during the preparation of this paper.

<!-- tomd:uncertain:L900-L928 -->

Bibliography

[P2414R12]
Paul E. McKenney, Maged Michael, Jens Maurer, Peter Sewell, Martin Uecker, Hans
Boehm, Hubert Tong, Niall Douglas, Thomas Rodgers, Will Deacon, Michael Wong,
David Goldblatt, Kostya Serebryany, Anthony Williams, Tom Scogland, JF Bastien,
Daniel Krügler, and David Tenty, “Pointer lifetime-end zap proposed solutions”,
2026
http://wg21.link/P2414R12

[P2434R5]
S. Davis Herring, “Nondeterministic pointer provenance”, 2026
http://wg21.link/P2434R5

[P3347R6]
Paul E. McKenney, Maged Michael, Jens Maurer, Peter Sewell, Martin Uecker, Hans
Boehm, Hubert Tong, Niall Douglas, Thomas Rodgers, Will Deacon, Michael Wong,
David Goldblatt, Kostya Serebryany, Anthony Williams, Tom Scogland, JF Bastien,
Jason McGuiness, and David Tenty, “Invalid/Prospective Pointer Operations”, 2026
http://wg21.link/P3347R6

[P3950R1]
Robert Leahy, “return_value & return_void Are Not Mutually Exclusive”, 2026
http://wg21.link/P3950R1

[P4101R1]
Barry Revzin, Peter Dimov, Daveed Vandevoorde, and Dan Katz, “Consteval-only
Values for C++26”, 2026
http://wg21.link/P4101R1

| [CWG2300] | Robert Haberlach, “Lambdas in multiple definitions” | `https://wg21.link/cwg2300` |
| --- | --- | --- |
| [CWG2392] | Tam S. B, “new-expression size check and constant evaluation” | `https://wg21.link/cwg2392` |

| [P3424R2] | Alisdair Meredith, “Deallocation Functions with Throwing Exception Specification | Are Ill-formed”, 2026 | `http://wg21.link/P3424R2` |
| --- | --- | --- | --- |
| [P3596R3] | Joshua Berne, Timur Doumler, Jens Maurer, and Shafik Yaghmour, “Undefined | Behavior and IFNDR Annexes”, 2026 | `http://wg21.link/P3596R3` |
| [P3899R3] | Jan Schultke and Matthias Kretz, “Clarify the behavior of floating-point overflow”, | 2026 | `http://wg21.link/P3899R3` |
