Lifetime extension for aggregate initialization
11.9.3 [class.base.init]
Ed Catmur

Created on 2022-12-18.00:00:00 last changed 13 months ago


Date: 2023-05-12.20:29:43

CWG 2023-05-12

CWG is soliciting the design guidance of EWG to resolve this issue, in particular for examples #3 and #6. For each of the presented examples, the standard should say whether the use is well-formed or not and whether the lifetime of the temporary is extended. It is also conceivable to implicitly delete the constructor being invoked (if any).

  • #1 is default-initialization, invoking the default constructor of B. This is ill-formed per 11.9.3 [class.base.init] paragraph 11. The rationale is that the temporary would not be lifetime-extended, causing a dangling reference as soon as the constructor returns. The presence of a hypothetically lifetime-extended temporary cannot be persisted from the constructor invocation to the eventual destruction of the object.
  • #2 is aggregate initialization; the temporary in the braced-init-list is lifetime-extended per 6.7.7 [class.temporary] paragraph 6. Since the temporary is created in the initializer and not inside a constructor invocation, tracking the lifetime of the temporary is equivalent to tracking the lifetime of the variable b2, so extending the lifetime of the temporary is feasible and avoids a dangling reference.
  • #3 might be perceived as value-initialization, but is in fact also aggregate initialization; the status quo wording lifetime-extend the temporary per 6.7.7 [class.temporary] paragraph 6. However, that is inconsistent with #4.
  • #4 is value-initialization; the default constructor is invoked per 9.4.1 [dcl.init.general] bullet, which is ill-formed similar to #1.
  • #5 is aggregate-by-parentheses initialization. Consistent with other parenthesized initialization, the lifetime of temporaries is not extended (6.7.7 [class.temporary] bullet 6.10).
  • #6 is also aggregate-by-parentheses initialization; a constructor is not invoked and the lifetime of the temporary is not extended per 6.7.7 [class.temporary] bullet 6.10, causing a dangling reference. However, despite the hidden temporary, this use is not ill-formed, because 11.9.3 [class.base.init] does not apply to aggregate initialization.
  • Finally, #7 shows a situation where, unlike #6, the short lifetime of the hidden temporary is not an issue.
CWG recommends to leave #3 as-is and to make #6 (and thus #7) ill-formed. The trigger is the use of a default member initializer for a reference member that causes binding a temporary to that reference.

Forwarded to EWG via cplusplus/papers#1511.

Date: 2023-03-31.22:55:31

Issue 1815 presented a subset of the following examples:

  struct A {};
  struct B { A&& a = A{}; };
  B b1;          // #1, default-initialization
  B b2{A{}};     // #2, aggregate initialization
  B b3{};        // #3, aggregate initialization
  B b4 = B();    // #4, value-initialization
  B b5(A{});     // #5, aggregate initialized via parentheses (9.4.1 [dcl.init.general] bullet
  struct C { int i; A&& a = A{}; };
  C c6(1);       // #6, aggregate initialized via parentheses
  A a7 = C(1).a; // #7, aggregate initialized via parentheses

Issue 1696 was adopted, ostensibly resolving issue 1815, but the wording changes in 11.9.3 [class.base.init] paragraph 11 affected only the behavior of constructors, not of aggregate initialization:

A temporary expression bound to a reference member from a default member initializer is ill-formed. [ Example:
  struct A {
    A() = default;          // OK
    A(int v) : v(v) { }     // OK
    const int& v = 42;      // OK
  A a1;     // error: ill-formed binding of temporary to reference
  A a2(1);  // OK, unfortunately
-- end example]

There is considerable implementation variance: #1 is rejected by clang, but accepted by gcc and EDG (with early destruction of the temporary); #2 is uniformly accepted and the lifetime of the temporary is extended; #3 is uniformly accepted, but only gcc, clang, and MSVC extend the lifetime of the temporary, whereas EDG does not.

Note that 9.4.1 [dcl.init.general] paragraph 7 specifies that default-initialization of aggregates is as-if the initializer () is used:

To default-initialize an object of type T means:
  • If T is a (possibly cv-qualified) class type (Clause 11 [class]), constructors are considered. The applicable constructors are enumerated ( [over.match.ctor]), and the best one for the initializer () is chosen through overload resolution (12.2 [over.match]). The constructor thus selected is called, with an empty argument list, to initialize the object.
  • If T is an array type, each element is default-initialized.
  • Otherwise, no initialization is performed.

Such treatment causes early destruction of temporaries per 6.7.7 [class.temporary] bullet 6.10:

The exceptions to this lifetime rule are:
  • ...
  • A temporary object bound to a reference element of an aggregate of class type initialized from a parenthesized expression-list (9.4 [dcl.init]) persists until the completion of the full-expression containing the expression-list.
Date User Action Args
2023-01-07 20:39:02adminsetmessages: + msg7126
2022-12-18 00:00:00admincreate