Skip to content

CWG2721 [basic.life] When exactly is "the storage which the object occupies [...] reused"? #278

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
zygoloid opened this issue Mar 23, 2023 · 5 comments · Fixed by cplusplus/draft#6328

Comments

@zygoloid
Copy link
Member

Full name of submitter (unless configured in github; will be published with the issue): Richard Smith

Reference (section label): [basic.life]/1.5

Link to reflector thread (if any):

Issue description:

[basic.life]/1.5 says:

The lifetime of an object o of type T ends when [...] the storage which the object occupies [...] is reused [...]

but what does that mean? In llvm/llvm-project#61562 we have two possible interpretations:

  1. In an expression new (p) T(x), the lifetime of *p ends when p is returned from void* operator new(size_t, void*), before x is evaluated.
  2. In an expression new (p) T(x), the lifetime of *p ends the moment the constructor body starts executing, after x is evaluated.

Note that interpretation (2) only works when the initialization is performed by a constructor or if T is a non-class type, and we must use interpretation (1) for aggregate initialization because the evaluation of x can directly initialize a part of the resulting object.

Suggested resolution:

To me, it seems that the most consistent and simplest model is that the lifetime of *p ends the moment that p is returned from an operator new function invoked by a new-expression, and in particular, the object no longer exists in the evaluation of the new-initializer.

Alternatively, we could say that the the lifetime ends as late as possible -- the moment anything actually starts initializing the new object (perhaps, when a constructor starts running or a value is stored for the object or any of its subobjects). That's probably going to be challenging for constant interpreters to model, though.

@languagelawyer
Copy link

Vote for 1

@frederick-vs-ja
Copy link

frederick-vs-ja commented Mar 24, 2023

Perhaps we can separate the bullet to make the intent of interpretation (1) clear:

  • [...] or, is reused by an object that is not nested within o ([intro.object]).
  • the initialization of an object that is not nested within o but reuses the storage occupied by o starts ([intro.object]).

Could this be interfered by implicit object creation? (Although implicit object creation can be considered not happening in constant evaluation.)

Edit: perhaps implicit creation should also be considered:

  • [...] or, is reused by an object that is not nested within o ([intro.object]).
  • the initialization or the lifetime of an object that is not nested within o but reuses the storage occupied by o starts ([intro.object]).

@davidstone
Copy link

As the opener of the original clang bug, I was surprised to see that it appeared to "work" on gcc. As much as I would have liked for this to be valid because it simplifies some code to avoid a self-move-assignment check, I also couldn't figure out how implementations could actually make this work with things like NRVO, which feels like the far more important optimization to support. So coming at this from a purely pragmatic instead of conceptual angle, it seems like a resolution like the proposed option 1 would be better.

To make sure I understand option 1 fully, this would mean that in new(p) T(x), the expression x can reference *p as usual, but the constructor of T must treat *p in the same it would treat any reference to an object whose lifetime has not begun, correct? So the following would also be ill-formed:

struct s {
    int m;
};

void f() {
    s x{1};
    new(&x) s(x.m);
}

@languagelawyer
Copy link

So the following would also be ill-formed

I think you mean would be UB, but yes

The invocation of the allocation function is sequenced before the evaluations of expressions in the new-initializer

(https://timsong-cpp.github.io/cppwp/n4868/expr.new#23.sentence-1)

So by the time the evaluation of x.m starts, x would rebind to the newly-created object.

@jensmaurer
Copy link
Member

CWG2721

@jensmaurer jensmaurer changed the title [basic.life] When exactly is "the storage which the object occupies [...] reused"? CWG2721 [basic.life] When exactly is "the storage which the object occupies [...] reused"? Apr 16, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

5 participants