Using a union to prevent destruction?

Related: How does =delete on destructor prevent stack allocation?

First, I realize this sort of thing is playing with fire, tempting UB. I'm asking to get a better understanding of corner-cases of unions.

As I understand it, if I have a class with a c'tor and d'tor and put it in a union, the compiler will force me to tell it what to do about construction and destruction. As in

#include <iostream>

struct S {
    int x;
    S() : x(-1) { std::cout << "S()" << std::endl; }
    ~S() { std::cout << "~S()" << std::endl; }
    static S& instance() {
        union DoNotDestruct {
            S value;
            DoNotDestruct() : value() {}
            ~DoNotDestruct() {}
        };
        static DoNotDestruct inst;
        return inst.value;
    }
};

int main() {
    return S::instance().x;
}

which just reports construction, not destruction.

However, it appears that if I have a deleted destructor, that doesn't work; I have to use placement-new:

#include <new>

// Placement-new version:
struct S0 {
    S0() {}
    ~S0() = delete;
    static S0& instance() {
        union DoNotDestruct {
            S0 value;
            DoNotDestruct() {
                // I believe value isn't constructed yet.
                new (&value) S0; // Placement-new.
            }
            ~DoNotDestruct() {} // Omit S0's d'tor.
        };
        static DoNotDestruct inst;
        return inst.value;
    }
};

// Init version:
struct S1 {
    S1() {}
    ~S1() = delete;
    static S1& instance() {
        union DoNotDestruct {
            S1 value;
            DoNotDestruct() : value{} {} // Why does this line want S1::~S1() to exist?
            ~DoNotDestruct() {} // Omit S1's d'tor.
        };
        static DoNotDestruct inst;
        return inst.value;
    }
};

https://godbolt.org/z/7r4ebszor

Here, S0 is happy, but S1 complains on the constructor line that S1::~S1() is deleted. Why?

I tried adding noexcept on the thought that in a class, if you have multiple members, the 0th member needs to be destructible if any subsequent c'tors throw. It looks like even a class with a deleted d'tor can't itself hold an indestructible member even if the c'tors are all noexcept. It gives the same error as the union: https://godbolt.org/z/jx3W1YEPf

1 answer

  • answered 2022-01-19 17:47 user17732522

    [class.dtor]/15 says that a program is ill-formed if a potentially invoked destructor is defined as deleted.

    [class.base.init]/12 says that a destructor of a potentially constructed subobject of a class is potentially invoked in a non-delegating constructor.

    [special]/7 says that all non-static data members are potentially constructed subobjects, although the sentence is qualified a bit weirdly I think ("For a class, [...]").

    I don't see any exceptions in this for single-member classes, unions or noexcept. So it seems to me that GCC is correct for S1.

    Clang is also the only compiler out of Clang, GCC, ICC and MSVC that doesn't reject this version.

    For S0 however, this reasoning would also apply, making it ill-formed as well. However none of the four compilers agrees on that, so maybe I am wrong.


    The quoted wording comes mostly from CWG issue 1424. Clang lists its implementation status currently as unknown (https://clang.llvm.org/cxx_dr_status.html), as does GCC (https://gcc.gnu.org/projects/cxx-dr-status.html).

How many English words
do you know?
Test your English vocabulary size, and measure
how many words do you know
Online Test
Powered by Examplum