c++language-lawyerc++20c++-conceptsoverload-resolution

How does overload resolution select from multiple prospective destructors?


After seeing this post: If a class has a destructor declared with a requires clause that evaluates to false, why is the class not trivially destructible? I have a separate question, and I haven't found a good answer for it yet.

The accepted answer says the class needs to declare two prospective destructors in order to compile correctly:

template<typename T>
class Optional
{
public:
  ~Optional() requires (!std::is_trivially_destructible_v<T>);
  ~Optional() = default;
};

Assuming the template is instantiated, and the requires constraint is satisfied, that means there would be 2 prospective destructors, correct? According to the C++ standard in [class.dtor]:

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. Destructor selection does not constitute a reference to, or odr-use ([basic.def.odr]) of, the selected destructor, and in particular, the selected destructor may be deleted ([dcl.fct.def.delete]).

So, my question is - if there are multiple prospective destructors, and they all must have an empty argument list, then HOW does overload resolution decide which prospective destructor becomes the selected destructor?

I've read:

But they don't answer my question. The accepted answer to the 1st one does state:

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.

Isn't that the case in the example above - two viable destructors? Or does overload resolution deem one to be "better" than the other?

I tried reading [over.match], but don't see the answer in it. Am I missing something?


Solution

  • Overload resolution selects the best viable function ([over.match.general]/3):

    If a best viable function exists and is unique, overload resolution succeeds and produces it as the result. Otherwise overload resolution fails and the invocation is ill-formed. When overload resolution succeeds, and the best viable function is not accessible in the context in which it is used, the program is ill-formed.

    Exactly how the best viable function is selected is complicated, but to prospective destructors, only [over.match.best.general]/(2.6) matters:

    Given these definitions, a viable function F1 is defined to be a better function than another viable function F2 if [...], and then

    • F1 and F2 are non-template functions and F1 is more partial-ordering-constrained than F2

    Since ~Optional() requires (!std::is_trivially_destructible_v<T>) has a constraint and ~Optional() does not, the former is considered more partial-ordering-constrained than the latter and thus better than the latter, and is eventually selected as the best viable function.