Consider:
#include <type_traits>
template <typename T>
struct A {
~A() requires(std::is_void_v<T>);
//~A() requires(!std::is_void_v<T>) = default;
};
template struct A<int>;
I'd think that's impossible for a class not to have a destructor and gcc 13.2 seems to agree with me but clang 17.0 and msvc 19.38 don't and complain:
[clang] error: no viable destructor found for class 'A<int>'
[msvc ] error C7653: 'A<int>': failed to select a destructor for the class
(Adding the commented line to the code works around this issue and make all compilers happy.)
Which compilers are correct? What are the relevant quotes from the Standard?
A (completed) class must always have a destructor. However, that destructor might be implicitly-declared, deleted or left undefined.
At the end of the class definition a destructor is chosen from the prospective destructors via overload resolution. If the user didn't declare any prospective destructor, then one is implicitly-declared. So there is always at least one candidate for this overload resolution. The program is ill-formed if the overload resolution fails.
So GCC is wrong and MSVC and Clang are correct. In your case there is no implicitly-declared prospective destructor, because you declared a prospective destructor manually. That single prospective destructor is not viable in overload resolution because its constraints are not satisfied. So overload resolution fails.
This is specified in (draft N4868) [class.dtor]/4.
If the selected destructor is defined as deleted, then the class still has a destructor, but it will be impossible to destroy objects of the class type, i.e. the class is also "indestructible". Any program trying to destroy such objects would be ill-formed.
Similarly, if the selected destructor is left undefined, then any attempt to destroy an object of the class type would render the program IFNDR (ill-formed, no diagnostic required) for violating the one-definition-rule.