Although scope plays a role the real issue is about object lifetime and more exactly for object with non-trivial initialization when does the lifetime begin.
This is closely related to Can initializing expression use the variable itself? and Is passing a C++ object into its own constructor legal?. Although my answers to those questions do not neatly answer this question, so it does not seem like a duplicate.
The key portion of the draft C++ standard we are concerned with here is section 3.8
[basic.life] which says:
The lifetime of an object is a runtime property of the object. An object is said to have non-trivial initialization
if it is of a class or aggregate type and it or one of its members is initialized by a constructor other than a trivial
default constructor. [ Note: initialization by a trivial copy/move constructor is non-trivial initialization. —
end note ] The lifetime of an object of type T begins when:
- storage with the proper alignment and size for type T is obtained, and
- if the object has non-trivial initialization, its initialization is complete.
So in this case we satisfy the first bullet, storage has been obtained.
The second bullet is where we find trouble:
- do we have non-trivial initialization
- and if so is the initialization complete
Non-trivial initialization case
We can get a base reasoning from defect report 363 which asks:
And if so, what is the semantics of the self-initialization of UDT?
For example
#include <stdio.h>
struct A {
A() { printf("A::A() %p\n", this); }
A(const A& a) { printf("A::A(const A&) %p %p\n", this, &a); }
~A() { printf("A::~A() %p\n", this); }
};
int main()
{
A a=a;
}
can be compiled and prints:
A::A(const A&) 0253FDD8 0253FDD8
A::~A() 0253FDD8
and the proposed resolution was:
3.8 [basic.life] paragraph 6 indicates that the references here are valid. It's permitted to take the address of a class object before it
is fully initialized, and it's permitted to pass it as an argument to
a reference parameter as long as the reference can bind directly.
[...]
So before the lifetime of an object begins we are limited in what we can do with an object. We can see from the defect report binding a reference to x
is valid as long as it binds directly.
What we can do is covered in section 3.8
(The same section and paragraph the defect report quotes) says (emphasis mine):
Similarly, before the lifetime of an object has started but after the
storage which the object will occupy has been allocated or, after the
lifetime of an object has ended and before the storage which the
object occupied is reused or released, any glvalue that refers to the
original object may be used but only in limited ways. For an object
under construction or destruction, see 12.7. Otherwise, such a glvalue
refers to allocated storage (3.7.4.2), and using the properties of the
glvalue that do not depend on its value is well-defined. The program
has undefined behavior if:
an lvalue-to-rvalue conversion (4.1) is applied to such a glvalue,
the glvalue is used to access a non-static data member or call a non-static member function of the
object, or
the glvalue is bound to a reference to a virtual base class (8.5.3), or
the glvalue is used as the operand of a dynamic_cast (5.2.7) or as the operand of typeid.
In your case we are accessing a non-static data member here, see emphasis above:
r = ...;
So if T
has non-trivial initialization then this line invokes undefined behavior and so would reading from r
which would also be an access, covered in defect report 1531.
If x
has static storage duration it will be zero-initialized but as far as I can tell this does not count as it's initialization is complete since the constructor would be called during dynamic initialization.
Trivial Initialization case
If T
has trivial initializaton then the lifetime begins once storage is obtained and writing to r
is well defined behavior. Although note that reading r
before it has initialized will invoke undefined behavior since it would produce an indeterminate value. If x
has static storage duration then it is zero-initialized and we don't have this issue.
Should it compile, in either cases whether you are invoking undefined behavior or not this allowed to compile. The compiler is not obligated to produce a diagnostic for undefined behavior although it may. It is only obligated to produce a diagnostic for ill-formed code which none of the troublesome cases here are.