Skip to content

[class.dtor] trivial union proposal added overly aggressive rule to delete a union destructor #722

Open
@brevzin

Description

@brevzin

Reference (section label): [class.dtor]

Link to reflector thread (if any):

Issue description:

P3074R7 in its latest revision tried to add symmetry for the constructor and destructor rules, on the premise that if the constructor constructs something with a non-trivial destructor the defaulted destructor should be deleted. i.e. if { U u; } works it should be sensible.

However, the wording right now says:

A defaulted destructor for a class X is defined as deleted if

  • [...],
  • X is a union and
    • overload resolution to select a constructor to default-initialize an object of type X either fails or selects a constructor that is either deleted or not trivial, or
    • X has a variant member V of class type M (or possibly multi-dimensional array thereof) where V has a default member initializer and M has a destructor that is non-trivial,
  • or, [...]

That means that, among other things, this type:

union U {
    U(int i) : i(i) { }
    int i;
};

now has a deleted destructor.

We should only delete the defaulted destructor for a union if:

  • there is a subobject S of class type M (or possibly multi-dimensional array thereof) where M has a destructor that is deleted, inaccessible from the defaulted destructor, or non-trivial, AND EITHER
    • overload resolution to select a constructor to default-initialize an object of type X either fails or selects a constructor that is either deleted or user-provided not trivial, OR
    • X has a variant member V of class type M (or possibly multi-dimensional array thereof) where V S has a default member initializer and M has a destructor that is non-trivial,

The user-provided addition is because we don't want to reject either of these:

union U1 { string s; U1* next = nullptr; };
union U2 { U2() = default; string s; U2* next = nullptr; };

Those default constructors aren't trivial, but this case is okay. It's only these cases that we want to reject:

union U3 { U3(); string s; U3* next; };
union U4 { string s = "hi"; U4* next; };

because we don't know what U3 might initialize — it might initialize s, so we preemptively delete the destructor in that case. And U4 we know we're initializing s, so we definitely want to reject.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions