This is an unofficial snapshot of the ISO/IEC JTC1 SC22 WG21 Core Issues List revision 114b. See http://www.open-std.org/jtc1/sc22/wg21/ for the official list.

2024-05-06


453. References may only bind to “valid” objects

Section: 9.3.4.3  [dcl.ref]     Status: DR     Submitter: Gennaro Prota     Date: 18 Jan 2004

[Accepted as a DR at the March, 2024 meeting.]

9.3.4.3 [dcl.ref] paragraph 4 says:

A reference shall be initialized to refer to a valid object or function. [Note: in particular, a null reference cannot exist in a well-defined program, because the only way to create such a reference would be to bind it to the "object" obtained by dereferencing a null pointer, which causes undefined behavior ...]

What is a "valid" object? In particular the expression "valid object" seems to exclude uninitialized objects, but the response to Core Issue 363 clearly says that's not the intent. This is an example (overloading construction on constness of *this) by John Potter, which I think is supposed to be legal C++ though it binds references to objects that are not initialized yet:

 struct Fun {
    int x, y;
    Fun (int x, Fun const&) : x(x), y(42) { }
    Fun (int x, Fun&) : x(x), y(0) { }
  };
  int main () {
    const Fun f1 (13, f1);
    Fun f2 (13, f2);
    cout << f1.y << " " << f2.y << "\n";
  }

Suggested resolution: Changing the final part of 9.3.4.3 [dcl.ref] paragraph 4 to:

A reference shall be initialized to refer to an object or function. From its point of declaration on (see 6.4.2 [basic.scope.pdecl]) its name is an lvalue which refers to that object or function. The reference may be initialized to refer to an uninitialized object but, in that case, it is usable in limited ways (6.7.3 [basic.life], paragraph 6) [Note: On the other hand, a declaration like this:
    int & ref = *(int*)0;
is ill-formed because ref will not refer to any object or function ]

I also think a "No diagnostic is required." would better be added (what about something like int& r = r; ?)

Proposed Resolution (October, 2004) [SUPERSEDED]:

(Note: the following wording depends on the proposed resolution for issue 232.)

Change 9.3.4.3 [dcl.ref] paragraph 4 as follows:

A reference shall be initialized to refer to a valid object or function. If an lvalue to which a reference is directly bound designates neither an existing object or function of an appropriate type (9.4.4 [dcl.init.ref]), nor a region of memory of suitable size and alignment to contain an object of the reference's type (6.7.2 [intro.object], 6.7.3 [basic.life], 6.8 [basic.types]), the behavior is undefined. [Note: in particular, a null reference cannot exist in a well-defined program, because the only way to create such a reference would be to bind it to the “object” empty lvalue obtained by dereferencing a null pointer, which causes undefined behavior. As does not designate an object or function. Also, as described in 11.4.10 [class.bit], a reference cannot be bound directly to a bit-field. ]

The name of a reference shall not be used in its own initializer. Any other use of a reference before it is initialized results in undefined behavior. [Example:

  int& f(int&);
  int& g();

  extern int& ir3;
  int* ip = 0;

  int& ir1 = *ip;     // undefined behavior: null pointer
  int& ir2 = f(ir3);  // undefined behavior: ir3 not yet initialized
  int& ir3 = g();
  int& ir4 = f(ir4);  // ill-formed: ir4 used in its own initializer
end example]

Rationale: The proposed wording goes beyond the specific concerns of the issue. It was noted that, while the current wording makes cases like int& r = r; ill-formed (because r in the initializer does not "refer to a valid object"), an inappropriate initialization can only be detected, if at all, at runtime and thus "undefined behavior" is a more appropriate treatment. Nevertheless, it was deemed desirable to continue to require a diagnostic for obvious compile-time cases.

It was also noted that the current Standard does not say anything about using a reference before it is initialized. It seemed reasonable to address both of these concerns in the same wording proposed to resolve this issue.

Notes from the April, 2005 meeting:

The CWG decided that whether to require an implementation to diagnose initialization of a reference to itself should be handled as a separate issue (504) and also suggested referring to “storage” instead of “memory” (because 6.7.2 [intro.object] defines an object as a “region of storage”).

Proposed Resolution (April, 2005) [SUPERSEDED]:

(Note: the following wording depends on the proposed resolution for issue 232.)

Change 9.3.4.3 [dcl.ref] paragraph 4 as follows:

A reference shall be initialized to refer to a valid object or function. If an lvalue to which a reference is directly bound designates neither an existing object or function of an appropriate type (9.4.4 [dcl.init.ref]), nor a region of storage of suitable size and alignment to contain an object of the reference's type (6.7.2 [intro.object], 6.7.3 [basic.life], 6.8 [basic.types]), the behavior is undefined. [Note: in particular, a null reference cannot exist in a well-defined program, because the only way to create such a reference would be to bind it to the “object” empty lvalue obtained by dereferencing a null pointer, which causes undefined behavior. As does not designate an object or function. Also, as described in 11.4.10 [class.bit], a reference cannot be bound directly to a bit-field. ]

Any use of a reference before it is initialized results in undefined behavior. [Example:

  int& f(int&);
  int& g();

  extern int& ir3;
  int* ip = 0;

  int& ir1 = *ip;     // undefined behavior: null pointer
  int& ir2 = f(ir3);  // undefined behavior: ir3 not yet initialized
  int& ir3 = g();
  int& ir4 = f(ir4);  // undefined behavior: ir4 used in its own initializer
end example]

Note (February, 2006):

The word “use” in the last paragraph of the proposed resolution was intended to refer to the description in 6.3 [basic.def.odr] paragraph 2. However, that section does not define what it means for a reference to be “used,” dealing only with objects and functions. Additional drafting is required to extend 6.3 [basic.def.odr] paragraph 2 to apply to references.

Additional note (May, 2008):

The proposed resolution for issue 570 adds wording to define “use” for references.

Note, January, 2012:

The resolution should also probably deal with the fact that the “one-past-the-end” address of an array does not designate a valid object (even if such a pointer might “point to” an object of the correct type, per 6.8.4 [basic.compound]) and thus is not suitable for the lvalue-to-rvalue conversion.

CWG 2023-11-06

We need a (possibly out-of-lifetime) object, not just a region of storage here. Empty lvalues do not exist. Otherwise, the direction is confirmed.

Proposed resolution (approved by CWG 2023-11-10) [SUPERSEDED]:

  1. Change in 7.2.1 [basic.lval] paragraph 11 as follows:

    An object of dynamic type Tobj is type-accessible through a glvalue of type Tref if Tref is similar (7.3.6 [conv.qual]) to:

    • Tobj,
    • a type that is the signed or unsigned type corresponding to Tobj, or
    • a char, unsigned char, or std:byte type.

    If a program attempts to access (3.1 [defns.access]) the stored value of an object through a glvalue whose type is not similar (7.3.6 [conv.qual]) to one of the following types through which it is not type-accessible, the behavior is undefined:.[ Footnote: ... ]
    • the dynamic type of the object,
    • a type that is the signed or unsigned type corresponding to the dynamic type of the object, or
    • a char, unsigned char, or std::byte type.
    If a program invokes a defaulted copy/move constructor or copy/move assignment operator for a union of type U with a glvalue argument that does not denote an object of type cv U within its lifetime, the behavior is undefined.
  2. Change in 7.6.1.3 [expr.call] paragraph 5 as follows:

    A type Tcall is call-compatible with a function type Tfunc if Tcall is the same type as Tfunc or if the type "pointer to Tfunc" can be converted to type "pointer to Tcall" via a function pointer conversion (7.3.14 [conv.fctptr]). Calling a function through an expression whose function type E is different from the function is not call-compatible with the type F of the called function's definition results in undefined behavior unless the type “pointer to F” can be converted to the type “pointer to E” via a function pointer conversion (7.3.14 [conv.fctptr]). [Note 4: The exception applies This requirement allows the case when the expression has the type of a potentially-throwing function, but the called function has a non-throwing exception specification, and the function types are otherwise the same. —end note]
  3. Change and split in 9.3.4.3 [dcl.ref] paragraph 5 as follows:

    There shall be no references to references, no arrays of references, and no pointers to references. The declaration of a reference shall contain an initializer (9.4.4 [dcl.init.ref]) except when the declaration contains an explicit extern specifier (9.2.2 [dcl.stc]), is a class member (11.4 [class.mem]) declaration within a class definition, or is the declaration of a parameter or a return type (9.3.4.6 [dcl.fct]); see 6.2 [basic.def]. A reference shall be initialized to refer to a valid object or function.

    Attempting to bind a reference to a function where the initializer is a glvalue whose type is not call-compatible (7.6.1.3 [expr.call]) with the type of the function's definition results in undefined behavior. Attempting to bind a reference to an object where the initializer is a glvalue through which the object is not type-accessible (7.2.1 [basic.lval]) results in undefined behavior. [Note 2: In particular, a null reference cannot exist in a well-defined program, because the only way to create such a reference would be to bind it to the “object” obtained by indirection through a null pointer, which causes undefined behavior. The object designated by such a glvalue can be outside its lifetime (6.7.3 [basic.life]). Because a null pointer value or a pointer past the end of an object does not point to an object, a reference in a well-defined program cannot refer to such things; see 7.6.2.2 [expr.unary.op]. As described in 11.4.10 [class.bit], a reference cannot be bound directly to a bit-field. —end note] An odr-use (6.3 [basic.def.odr]) of a reference that does not happen after (6.9.2.2 [intro.races]) its initialization results in undefined behavior. [ Example:

    int &f(int&);
    int &g();
    extern int &ir3;
    int *ip = 0;
    int &ir1 = *ip;    // undefined behavior: null pointer
    int &ir2 = f(ir3); // undefined behavior: ir3 not yet initialized
    int &ir3 = g();
    int &ir4 = f(ir4); // undefined behavior: ir4 used in its own initializer
    
    -- end example ]

Additional notes (November, 2023):

An odr-use is a property of the program, not an evaluation that participates in the "happens before" relation.

Proposed resolution (approved by CWG 2024-03-20):

  1. Change in 7.2.1 [basic.lval] paragraph 11 as follows:

    An object of dynamic type Tobj is type-accessible through a glvalue of type Tref if Tref is similar (7.3.6 [conv.qual]) to:

    • Tobj,
    • a type that is the signed or unsigned type corresponding to Tobj, or
    • a char, unsigned char, or std:byte type.

    If a program attempts to access (3.1 [defns.access]) the stored value of an object through a glvalue whose type is not similar (7.3.6 [conv.qual]) to one of the following types through which it is not type-accessible, the behavior is undefined:.[ Footnote: ... ]
    • the dynamic type of the object,
    • a type that is the signed or unsigned type corresponding to the dynamic type of the object, or
    • a char, unsigned char, or std::byte type.
    If a program invokes a defaulted copy/move constructor or copy/move assignment operator for a union of type U with a glvalue argument that does not denote an object of type cv U within its lifetime, the behavior is undefined.
  2. Change in 7.6.1.3 [expr.call] paragraph 5 as follows:

    A type Tcall is call-compatible with a function type Tfunc if Tcall is the same type as Tfunc or if the type "pointer to Tfunc" can be converted to type "pointer to Tcall" via a function pointer conversion (7.3.14 [conv.fctptr]). Calling a function through an expression whose function type E is different from the function is not call-compatible with the type F of the called function's definition results in undefined behavior unless the type “pointer to F” can be converted to the type “pointer to E” via a function pointer conversion (7.3.14 [conv.fctptr]). [Note 4: The exception applies This requirement allows the case when the expression has the type of a potentially-throwing function, but the called function has a non-throwing exception specification, and the function types are otherwise the same. —end note]
  3. Change and split in 9.3.4.3 [dcl.ref] paragraph 5 as follows:

    There shall be no references to references, no arrays of references, and no pointers to references. The declaration of a reference shall contain an initializer (9.4.4 [dcl.init.ref]) except when the declaration contains an explicit extern specifier (9.2.2 [dcl.stc]), is a class member (11.4 [class.mem]) declaration within a class definition, or is the declaration of a parameter or a return type (9.3.4.6 [dcl.fct]); see 6.2 [basic.def]. A reference shall be initialized to refer to a valid object or function.

    Attempting to bind a reference to a function where the converted initializer is a glvalue whose type is not call-compatible (7.6.1.3 [expr.call]) with the type of the function's definition results in undefined behavior. Attempting to bind a reference to an object where the converted initializer is a glvalue through which the object is not type-accessible (7.2.1 [basic.lval]) results in undefined behavior. [Note 2: In particular, a null reference cannot exist in a well-defined program, because the only way to create such a reference would be to bind it to the “object” obtained by indirection through a null pointer, which causes undefined behavior. The object designated by such a glvalue can be outside its lifetime (6.7.3 [basic.life]). Because a null pointer value or a pointer past the end of an object does not point to an object, a reference in a well-defined program cannot refer to such things; see 7.6.2.2 [expr.unary.op]. As described in 11.4.10 [class.bit], a reference cannot be bound directly to a bit-field. —end note] The behavior of an evaluation of a reference (7.5.4 [expr.prim.id], 7.6.1.5 [expr.ref]) that does not happen after (6.9.2.2 [intro.races]) the initialization of the reference is undefined. [ Example:

    int &f(int&);
    int &g();
    extern int &ir3;
    int *ip = 0;
    int &ir1 = *ip;    // undefined behavior: null pointer
    int &ir2 = f(ir3); // undefined behavior: ir3 not yet initialized
    int &ir3 = g();
    int &ir4 = f(ir4); // undefined behavior: ir4 used in its own initializer
    
    char x alignas(int);
    int &ir5 = *reinterpret_cast<int *>(&x);  // undefined behavior: initializer refers to char object
    
    -- end example ]