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

2024-11-11


2868. Self-references in trivially copyable objects as function return values

Section: 6.7.7  [class.temporary]     Status: open     Submitter: Richard Smith     Date: 2018-02-16

Consider:

  struct A {
    A *children;
    long long arr[100];

    A() : children() {}
    A(int) : children(this) {}
  };

  __attribute__((noinline))
  A Foo() {
    return A(0);   // #1
  }

  A x[3] = {};

  void Bar(int n) {
    A a = Foo();
    for (int i = 0; i < n; i++) {
      a.children[i].children = x;     // #2
    }
  }

  int main() {
    Bar(3);
    return x[0].children || !x[1].children || !x[2].children;
  }

It ought to be valid to hoist the load of a.children out of the loop. However, guaranteed copy elision is required to apply to #1, making the A objects in Foo and Bar be the same object. An additional temporary copy can be made per 6.7.7 [class.temporary] paragraph 3, but that does not cause the children member of the a variable to become invalid.

Suggested resolution:

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

When an object of class type X is passed to or returned from a function, if X has at least one eligible copy or move constructor (11.4.4 [special]), each such constructor is trivial, and the destructor of X is either trivial or deleted, implementations are permitted to create a temporary object to hold the function parameter or result object. The temporary object is constructed from the function argument or return value, respectively, and the function's parameter or return object is initialized as if by using the eligible trivial constructor to copy the temporary (even if that constructor is inaccessible or would not be selected by overload resolution to perform a copy or move of the object). The temporary object may occupy the storage that the parameter or return object is going to occupy, but pointers that pointed to the temporary object become invalid pointer values (6.8.4 [basic.compound]) when the parameter or return object is constructed and do not point to the parameter or return object.

CWG 2024-05-03

For return values, CWG favors replacing the exception in 6.7.7 [class.temporary] paragraph 3 with an amendment to the specification of guaranteed copy elision in 9.4.1 [dcl.init.general] bullet 16.6.1. It is unclear whether parameter values should be treated the same.

Possible resolution:

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

    When an object of class trivially returnable (8.7.4 [stmt.return]) type X is passed to or returned from a function, if X has at least one eligible copy or move constructor (11.4.4 [special]), each such constructor is trivial, and the destructor of X is either trivial or deleted, implementations are permitted to create a temporary object to hold the function parameter or result object. The temporary object is constructed from the function argument or return value, respectively, and the function's parameter or return object is initialized as if by using the eligible trivial constructor to copy the temporary (even if that constructor is inaccessible or would not be selected by overload resolution to perform a copy or move of the object).
  2. Split and change 8.7.4 [stmt.return] paragraph 2 as follows:

    The expr-or-braced-init-list of a return statement is called its operand. A return statement with no operand shall be used only in a function whose return type is cv void, a constructor (11.4.5 [class.ctor]), or a destructor (11.4.7 [class.dtor]). A return statement with an operand of type void shall be used only in a function that has a cv void return type. A return statement with any other operand shall be used only in a function that has a return type other than cv void; the return statement initializes the returned reference or prvalue result object of the (explicit or implicit) function call by copy-initialization (9.4 [dcl.init]) from the operand. [Note 1: A constructor or destructor does not have a return type. —end note]

    A type T is trivially returnable if T is a class type that has at least one eligible copy or move constructor (11.4.4 [special]), each such constructor is trivial, and the destructor of T is either trivial or deleted. If a function has a return type R other than cv void:

    • If R is trivially returnable, the return statement initializes a first temporary object from the operand (7.3.5 [conv.rval]). Then, a second temporary object is constructed from the first temporary object, using an eligible trivial constructor for the copy. Then, the first temporary object is destroyed and the duration of its storage ends. [ Note: A pointer value pointing to the first temporary object becomes an invalid pointer value (6.8.4 [basic.compound]). -- end note ] Finally, the prvalue result object of the function call is initialized from the second temporary object, using an eligible trivial constructor for the copy, and the second temporary object is destroyed. In both cases, an eligible trivial constructor is used even if that constructor is inaccessible or would not be selected by overload resolution to perform a copy or move of the object.
    • Otherwise, the return statement initializes the returned reference or prvalue result object of the (explicit or implicit) function call by copy-initialization (9.4 [dcl.init]) from the operand.
    [ Note 2: A return statement can involve an invocation of a constructor to perform a copy or move of the operand if it is not a prvalue or if its type differs from the return type of the function. A copy operation associated with a return statement can be elided or converted to a move operation if an automatic storage duration variable is returned (11.9.6 [class.copy.elision]). —end note]