Current draft standard says (previous standards have similar wording) in [basic.life/1]:
The lifetime of an object or reference is a runtime property of
I interpret
The lifetime of an object of type
T
begins when...
to mean
Given that a program creates an object of
T
, the following describes when that object's lifetime is said to begin...
and not
If the following conditions are satisfied, then an object of type
T
exists, and its lifetime begins when...
That is, there's an implicit additional condition that the object is "created" in some way described in [intro.object]/1. But the paragraph [basic.life]1/ does not mean to by itself imply that any object exist, only one of the properties of objects that do exist.
So for your declaration, the text describes the beginning of the lifetimes of one object of type char[sizeof(int)]
and one or more objects of type char
(even if the declaration is a statement in a block scope and there is no initialization), but since there is no object of type int
implied to exist, we won't say anything about the lifetime of such an object.
You cannot begin the lifetime of a object unless the object has been created. And [intro.object]/1 defines the only ways in which objects can be created:
An object is created by a definition (6.1), by a new-expression (8.3.4), when implicitly changing the active member of a union (12.3), or when a temporary object is created (7.4, 15.2).
The object created by this definition is of type char[]
. Therefore, that is the only object whose lifetime begins. And no other objects are created by this construct.
To lend credence to this interpretation, the proposal for C++20 P0593 exists whose primary purpose is to allow that very declaration to implicitly create other such objects.
Comments:
The condition in (1.2) still bothers me. Why is it there?
It is there because it cannot say "the initialization is complete" for an object that doesn't undergo initialization.
suppose, that I have a
new(obj) int
afterwards. That clearly creates anint
object. But before that,obj
has obtained the necessary storage.
No, the declaration of obj
obtained storage for an object of type char[]
. What obtains storage for the int
object being created is new(obj)
. Yes, the placement-new expression obtains storage for the object that it creates. Just like a declaration of a variable obtains storage for the object it creates.
Just because that storage happens to exist already doesn't mean it isn't being obtained.
Because the Standard deliberately refrains from requiring that all implementations be suitable for all purposes, it will often be necessary for quality implementations intended for various purposes to guarantee the behavior of code over which the Standard itself would impose no requirements.
If some type T
supports implicit object creation and a program converts the address of some object to a T*
, a high-quality implementation which is intended to support low-level programming concepts without requiring special syntax will behave as though such conversion creates an object of type T
in cases where that would allow the program to have defined behavior, but would not implicitly create such objects is cases where doing so would not be necessary but would instead result in Undefined Behavior by destroying other objects.
Thus, if float
and uint32_t
are the same size and have the same alignment requirements, then given e.g.
alignas(uint32_t) char obj[sizeof(uint32_t)];
float *fp = (float*)obj;
*fp = 1.0f;
uint32_t *up = (uint32_t*)obj;
The initialization of fp
would create a float
because that would be needed to make the assignment to *fp
work. If up
will be used in a fashion that would require a uint32_t
to exist there, the assignment to up
could create one while destroying the float
that was there. If up
isn't used in such a fashion, but fp
is used in a way that would require that the float
still exist, that float
would still exist. If both pointers are used in ways that would require that the respective objects still exist, even a quality compiler intended for low-level programming might be incapable of handling that possibility.
Note that implementations which are not particularly suitable for low-level programming may not support the semantics described here. The authors of the Standard allows compiler writers to support such semantics or not, based upon whether they are necessary for their compilers' intended purposes; unfortunately, there is not as yet any standard way to distinguish compilers that are suitable for such purposes from those that aren't.