Is there any practical reason to check if something is destructible before calling its destructor?

Viewed 130

So I've been messing around trying to implement a variant/tagged union class and needed a way to write generic destructors and made what I thought was a silly mistake forgetting that some types don't have destructors, doing something like

template<typename T> 
void destruct(T& thing) {
    thing.~T();
}

Yet, this worked fine even with types that didn't have destructors, like int or struct A {int b;};. I still think it's more readable and easier to reason with something that uses something like this

template<typename T>
void destruct(T& thing) {
    if constexpr(std::is_destructible<T>::value) {
        thing.~T();
    }
}

But is there actually any difference between the code? The first one feels pretty undefined behavoiry/just wrong to me.

2 Answers

Yet, this worked fine even with types that didn't have destructors, like int or struct A {int b;};

Those are examples of types that are trivially destructible. It is well defined to invoke their "destructor". It has no effects.

But is there actually any difference between the code?

Only for types that are not destructible. Trivially destructible types are destructible.

For non-destructible types such as void, function types, or types with ~T() = delete;, the first function is ill-formed while the latter is well-formed with empty body. It depends on the use case which one is more useful, but silently ignoring attempt to destroy something that is not destructible seems dubious to me.

You don't need to check. In this context for a type like int it translates to a pseudo-destructor call. The result is a near no-op

[expr.pseudo]

1 The use of a pseudo-destructor-name after a dot . or arrow -> operator represents the destructor for the non-class type denoted by type-name or decltype-specifier. The result shall only be used as the operand for the function call operator (), and the result of such a call has type void. The only effect is the evaluation of the postfix-expression before the dot or arrow.

2 The left-hand side of the dot operator shall be of scalar type. The left-hand side of the arrow operator shall be of pointer to scalar type. This scalar type is the object type. The cv-unqualified versions of the object type and of the type designated by the pseudo-destructor-name shall be the same type. Furthermore, the two type-names in a pseudo-destructor-name of the form

nested-name-specifieropttype-name :: ~ type-name

shall designate the same scalar type (ignoring cv-qualification).

This sort of expression exists in the language deliberately, in order to make the writing of generic code easier. So your destruct is fine without the if.

As an aside, you might be interested to know that the standard library has a function like that. It's std::destroy_at. Other than handling arrays as a special case, it pretty much does what you do already.

Related