C++ lifetime of union member
Asked Answered
C

3

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!

Coauthor answered 10/9, 2019 at 20:33 Comment(3)
You already have it: its initialization (if any) is complete (including vacuous initialization) ([dcl.init]),. [dcl.init] talks about newMuirhead
@Muirhead 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.Coauthor
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!Rothschild
R
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)

Rutaceous answered 10/9, 2019 at 22:55 Comment(2)
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 rulesCoauthor
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.Rothschild
M
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).

Monkhood answered 17/9, 2019 at 15:41 Comment(5)
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?Coauthor
@Coauthor 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?Monkhood
[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?Coauthor
But then what used to happen in historical C++? Nothing? Compilers got it right because it goes w/o saying? :oRothschild
@Coauthor [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.Monkhood
R
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.

Rothschild answered 22/9, 2019 at 4:1 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.