8

In the current version of the C++ standard draft, [basic.life]/1 states:

The lifetime of an object or reference is a runtime property of the object or reference. A variable is said to have vacuous initialization if it is default-initialized and, if it is of class type or a (possibly multi-dimensional) array thereof, that class type has a trivial default constructor. The lifetime of an object of type T begins when:

  • storage with the proper alignment and size for type T is obtained, and

  • its initialization (if any) is complete (including vacuous initialization) ([dcl.init]),

except that if the object is a union member or subobject thereof, its lifetime only begins if that union member is the initialized member in the union ([dcl.init.aggr], [class.base.init]), or as described in [class.union]. [...]

From that paragraph I understand that the only way a member of a union begins its lifetime is if:

  • that member "is the initialized member in the union" (e.g. if it is referenced in a mem-initializer), or
  • some other way mentioned in [class.union]

However, the only normative paragraph in [class.union] that specifies how a union member can begin its lifetime is [class.union]/5 (but it only applies to specific types, i.e. either non-class, non-array, or class type with a trivial constructor that is not deleted, or array of such types).

The next paragraph, [class.union]/6 (comprising a note and an example, therefore it contains no normative text), describes a way to change the active member of a union, by using a placement new-expression, such as new (&u.n) N;, where

struct N { N() { /* non-trivial constructor */ } };
struct M { M() { /* non-trivial constructor */ } };

union 
{
    N n;
    M m;
} u;

My question is where in the standard is it specified that new (&u.n) N; begins the lifetime of u.n?

Thank you!

Community
  • 1
  • 1
user42768
  • 1,951
  • 11
  • 22
  • 1
    You already have it: *its initialization (if any) is complete (including vacuous initialization) ([dcl.init]),*. [dcl.init] talks about `new` – NathanOliver Sep 10 '19 at 20:37
  • 3
    @NathanOliver The emphasized part of [basic.life]/1 introduces additional conditions for beginning the lifetime of a union member. At least this is how I read it. – user42768 Sep 10 '19 at 20:42
  • Unions are another deeply troubling area in core C++: 1) there were no explicit rules for unions for a very long time; 2) "an lvalue refers to an object" is a commonly accepted trope, even in expert C++ circles; it's by definition incorrect for a union member being assigned. 3) "an lvalue refers to an object" is explicitly the explanation why you can't dereference a null ptr. C++ is a mess! – curiousguy Sep 21 '19 at 14:01

3 Answers3

2

An important rule regarding this is:

[class.union]/1

In a union, a non-static data member is active if its name refers to an object whose lifetime has begun and has not ended ([basic.life]). ...

As far as this rule is considered, the active member could change at any time a member object begins its lifetime. The rule [class.union]/5 further allows changing the active member also by assigning to a non-active member of a limited set of types. The lack of a separate rule for placement new by itself doesn't disallow changing the member. If it begins the lifetime of the member, then the member is the active member of the union.

So, [basic.life/1] says that the lifetime of the member begins only if [class.union] says so1, and [class.union/1] says that the member is active only if its lifetime has begun2. This does seem like a bit of a catch-22.

My best attempt at reading the rules in a way that makes sense is to interpret that placement-new begins the lifetime of the member, therefore [class.union/1] applies, and therefore "or as described in [class.union]" applies and therefore the highlighted exception doesn't apply. Next I would like to say therefore the lifetime begins, but that logic is circular.

The non-normative [class.union]/6 makes it quite clear that the placement new is intended to be allowed, but the normative rules are tangled. I would say that the wording could be improved.


1 (or when the union is initialised with that member, which isn't the case we are considering)

2 (or after assignment as per [class.union]/5, which isn't the case we are considering)

Community
  • 1
  • 1
eerorika
  • 232,697
  • 12
  • 197
  • 326
  • Thank you for your answer. I think [class.union]/1 does not say anything about when the lifetime of a union member begins, it just describes how the active member of the union is determined. The way I see the "rule flow" is the following: [basic.life]/1 states that the lifetime of a union member begins if it is the "initialized member" or as described by [class.union]. Next, the only *normative* text in [class.union] that states when the lifetime of a union member begins is paragraph 5. [class.union]/1 just uses the "conclusion" reached using the previous rules – user42768 Sep 11 '19 at 05:03
  • This is yet another reminding that the statement "an lvalue refers to an object" (and who can say he has never heard it from an "expert"?) **is false**, unless objects exist before their lifetimes has started, which means the other slogan "objects can't overlap (except subobjects)" is false. If you have been reading serious C++ literature, you almost certainly have read both statements. This is a problem. – curiousguy Sep 21 '19 at 13:56
0

My question is where in the standard is it specified that new (&u.n) N; begins the lifetime of u.n?

Nowhere. Placement new creates a new object which becomes union member subobject per [intro.object]/2:

If an object is created in storage associated with a member subobject or array element e (which may or may not be within its lifetime), the created object is a subobject of e's containing object if:
— the lifetime of e's containing object has begun and not ended, and
— the storage for the new object exactly overlays the storage location associated with e, and
— the new object is of the same type as e (ignoring cv-qualification).

Language Lawyer
  • 3,378
  • 1
  • 12
  • 29
  • Thank you for your answer. So the object created by the `placement new-expression` is not a _union member_, therefore [basic.life]/1 does not apply to it? – user42768 Sep 17 '19 at 19:53
  • @user42768 The newly-created object is a member subobject of a union if the criteria in [intro.object]/2 are met. Do you mean something else (not member subobject) by italicizing *union member*? – Language Lawyer Sep 17 '19 at 20:59
  • [basic.life]/1 states that a _union member_ (I am referring to this meaning of union member) only begins its lifetime under special conditions. So after your answer I was interpreting [basic.life]/1 as following: the initial object that is referenced by a union member only begins its lifetime under special conditions (however any other object that replaces it is not subject to these conditions). Is this right? – user42768 Sep 18 '19 at 05:43
  • But then what used to happen in historical C++? Nothing? Compilers got it right because it goes w/o saying? :o – curiousguy Sep 21 '19 at 13:49
  • @user42768 [class.union] does not seem to say that lifetime of an existing object starts. It says that a new object is created. I see you've already opened an issue. – Language Lawyer Sep 28 '19 at 13:34
0

C++ can't define unions at that point, because:

  • lvalues by definition must refer to an object
  • objects have a lifetime; it isn't clear what's a pre-lifetime object
  • in theory two unrelated objects can't be at the same address, but pre-lifetime objects can, so there is no such thing as pre-lifetime object

So mutable unions can't be well defined in C++, end of story.

curiousguy
  • 8,038
  • 2
  • 40
  • 58