c++language-lawyerc++20type-traitsc++-concepts

If a class has a destructor declared with a requires clause that evaluates to false, why is the class not trivially destructible?


I'm playing with c++20 concepts

https://godbolt.org/z/931xaeY45

#include <type_traits>

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

};

#include <cstdio>
int main()
{
    const char* bool_value[] = {"false", "true"};
    
    bool is_trivial = std::is_trivial_v<Optional<int>>;
    bool is_trivial_destructable = std::is_trivially_destructible_v<Optional<int>>;
    
    std::printf("std::is_trivial_v<Optional<int>> = %s\n", bool_value[is_trivial]);
    std::printf("std::is_trivial_destructable<Optional<int>> = %s\n", bool_value[is_trivial_destructable]);
}

Output:

std::is_trivial_v<Optional<int>> = false
std::is_trivial_destructable<Optional<int>> = false

But, I expected that the destructor doesn't get instantiated, because for this situation requires (false) is generated for template parameter int.

Q: Why does ~Optional() requires (false) make class Optional non-trivial?


Solution

  • My read of the standard, and MSVC, says that this is a compile error.

    In C++20, this:

    ~Optional() requires (!std::is_trivially_destructible_v<T>);
    

    is a "prospective destructor". It may or may not become the actual destructor based on a series of rules. But what really matters is this rule:

    If a class has no user-declared prospective destructor, a prospective destructor is implicitly declared as defaulted ([dcl.fct.def]).

    Your class has a "user-declared prospective destructor". Therefore, there is no "implicitly declared as defaulted" constructor ever.

    That's important because this rule kicks in:

    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.

    Since T is trivially destructible, overload resolution can't call your prospective destructor, since the requires constraint kills it. And, there are no other destructors to find. So, it isn't supposed to compile, and GCC may simply not yet implement the rule correctly.

    Regardless, you need to provide a defaulted alternative destructor:

    ~Optional() requires (!std::is_trivially_destructible_v<T>);
    ~Optional() = default;