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-06-28


2867. Order of initialization for structured bindings

Section: 9.6  [dcl.struct.bind]     Status: ready     Submitter: Richard Smith     Date: 2023-02-03

Consider:

  auto [a, b] = f(X{});

If X is a tuple-like type, this is transformed to approximately the following:

  auto e = f(X{});
  T1 &a = get<0>(std::move(e));
  T2 &b = get<1>(std::move(e));

However, the sequencing of the initializations of e, a, and b is not specified. Further, the temporary X{} should be destroyed after the initializations of a and b.

Possible resolution [SUPERSEDED]:

  1. Change in 6.9.1 [intro.execution] paragraph 5 as follows:

    A full-expression is
    • an unevaluated operand (7.2.3 [expr.context]),
    • a constant-expression (7.7 [expr.const]),
    • an immediate invocation (7.7 [expr.const]),
    • an init-declarator (9.3 [dcl.decl]) or a mem-initializer (11.9.3 [class.base.init]), including the constituent expressions of the initializer,
    • the initializers for all uniquely-named variables introduced by a structured binding declaration (9.6 [dcl.struct.bind]), including the constituent expressions of all initializers,
    • an invocation of a destructor generated at the end of the lifetime of an object other than a temporary object (6.7.7 [class.temporary]) whose lifetime has not been extended, or
    • an expression that is not a subexpression of another expression and that is not otherwise part of a full-expression.
    ...
  2. Change in 9.6 [dcl.struct.bind] paragraph 4 as follows:

    ... Each vi is the name of an lvalue of type Ti that refers to the object bound to ri; the referenced type is Ti. The initialization of e is sequenced before the initialization of any ri. The initialization of ri is sequenced before the initialization of rj if i < j.

CWG 2024-05-03

CWG tentatively agreed that all temporaries in a structured binding (including those from default arguments of get invocations) should persist until the semicolon on the declaration as written.

Possible resolution [SUPERSEDED]:

  1. Change in 6.9.1 [intro.execution] paragraph 5 as follows:

    A full-expression is
    • an unevaluated operand (7.2.3 [expr.context]),
    • a constant-expression (7.7 [expr.const]),
    • an immediate invocation (7.7 [expr.const]),
    • an init-declarator (9.3 [dcl.decl]) , an initializer of a structured binding declaration (9.1 [dcl.pre]), or a mem-initializer (11.9.3 [class.base.init]), including the constituent expressions of the initializer,
    • an invocation of a destructor generated at the end of the lifetime of an object other than a temporary object (6.7.7 [class.temporary]) whose lifetime has not been extended, or
    • an expression that is not a subexpression of another expression and that is not otherwise part of a full-expression.
    ...
  2. Change in 9.6 [dcl.struct.bind] paragraph 4 as follows:

    ... Each vi is the name of an lvalue of type Ti that refers to the object bound to ri; the referenced type is Ti. The initialization of e is sequenced before the initialization of any ri. The initialization of each ri is sequenced before the initialization of any rj where i < j.
  3. Append a new paragraph at the end of 9.6 [dcl.struct.bind] as follows:

    ... [ Example: ... The type of the id-expression x is "int", the type of the id-expression y is "const volatile double". -- end example ]

    The initialization of e and of any ri are sequenced before the destruction of any temporary object introduced by the initializer for e or by the initializers for the ri. The temporary objects are destroyed in the reverse order of their construction.

CWG 2024-06-14

The specification for the lifetime of temporaries should be moved to 6.7.7 [class.temporary].)

Proposed resolution (approved by CWG 2024-06-28):

  1. Change in 6.7.7 [class.temporary] paragraph 5 as follows:

    There are four five contexts in which temporaries are destroyed at a different point than the end of the full-expression. ...
  2. Insert a new paragraph after 6.7.7 [class.temporary] paragraph 7 as follows:

    The fourth context is when a temporary object is created in the for-range-initializer of a range-based for statement. If such a temporary object would otherwise be destroyed at the end of the for-range-initializer full-expression, the object persists for the lifetime of the reference initialized by the for-range-initializer.

    The fifth context is when a temporary object is created in a structured binding declaration (9.6 [dcl.struct.bind]). Any temporary objects introduced by the initializers for the variables with unique names are destroyed at the end of the structured binding declaration.

  3. Change in 6.9.1 [intro.execution] paragraph 5 as follows:

    A full-expression is
    • an unevaluated operand (7.2.3 [expr.context]),
    • a constant-expression (7.7 [expr.const]),
    • an immediate invocation (7.7 [expr.const]),
    • an init-declarator (9.3 [dcl.decl]) (including such introduced by a structured binding (9.6 [dcl.struct.bind])) or a mem-initializer (11.9.3 [class.base.init]), including the constituent expressions of the initializer,
    • an invocation of a destructor generated at the end of the lifetime of an object other than a temporary object (6.7.7 [class.temporary]) whose lifetime has not been extended, or
    • an expression that is not a subexpression of another expression and that is not otherwise part of a full-expression.
    ...
  4. Change in 9.6 [dcl.struct.bind] paragraph 4 as follows:

    ... Each vi is the name of an lvalue of type Ti that refers to the object bound to ri; the referenced type is Ti. The initialization of e is sequenced before the initialization of any ri. The initialization of each ri is sequenced before the initialization of any rj where i < j.