Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What is a "prospective destructor" in C++20?

It seems that in C++20 something called a "prospective destructor" was introduced. In C++17 [class.dtor]:

  1. 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:

  1. 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:

  1. 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.

like image 361
janekb04 Avatar asked Feb 04 '21 23:02

janekb04


1 Answers

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.

like image 168
Nicol Bolas Avatar answered Oct 05 '22 23:10

Nicol Bolas