It seems that in C++20 something called a "prospective destructor" was introduced. In C++17 [class.dtor]:
- In a declaration of a destructor, the declarator is a function declarator (11.3.5) of the form
ptr-declarator ( parameter-declaration-clause ) noexcept-specifier(opt) attribute-specifier-seq(opt)
In C++20 this got changed to this:
- A declaration whose declarator-id has an unqualified-id that begins with a ~ declares a prospective destructor; its declarator shall be a function declarator ([dcl.fct]) of the form
ptr-declarator ( parameter-declaration-clause ) noexcept-specifier(opt) attribute-specifier-seq(opt)
So what is this "prospective destructor"? Well the standard doesn't seem to clarify, at least for me:
- At the end of the definition of a class, overload resolution is performed among the prospective destructors declared in that class with an empty argument list to select the destructor for the class, also known as the selected destructor. The program is ill-formed if overload resolution fails.
What is the reason that this new concept of a "prospective destructor" got introduced? What does it even mean? How does it change code? What does it allow to do?
I think that perhaps this is meant to be used in template metaprogramming or maybe has do to something with SFINAE, but theses are just guesses.
It means what it says it means. It is a "destructor", modified by English word "prospective", which means "potential, likely, or expected". So it's a potential destructor.
The purpose of the concept is to allow... concepts.
A destructor is a special member function with special properties and behaviors. But most importantly, you're only allowed to have one, and you have to know which one you have pretty much immediately when you're using a class (unlike even default constructors).
However, if you want to have different destructor implementations based on whether the class's template parameters satisfy some concept, that means you have to have multiple destructor functions. They would be differentiated based on requires
clauses.
Consider std::optional<T>
. The destructor of optional<T>
must be trivial if T
has a trivial destructor. But the non-trivial form needs to explicitly call a non-trivial destructor if the optional
is engaged. So that's two function bodies: = default
and { /*actual code*/ }
.
Historically, this is done via using a base class or member type that is specialized on whether T
has a trivial destructor. But a proper requires
clause could make it much easier to implement.
Which destructor declaration survives is determined later, per the part of the standard you quoted. Doing overload resolution requires doing template substitution on all of the "prospective destructors". This causes any such destructors whose requires
clauses fail to disappear. If this process resolves down to a single destructor, then that's the actual destructor. And if it resolves to multiple destructors, then the code is ill-formed.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With