https://gcc.gnu.org/g:0629924777ea20d56d9ea40c3915eb0327a22ac7
commit r16-944-g0629924777ea20d56d9ea40c3915eb0327a22ac7 Author: Jason Merrill <ja...@redhat.com> Date: Wed May 28 11:42:00 2025 -0400 c++: add __is_*destructible builtins [PR107600] Typically "does this class have a trivial destructor" is the wrong question to ask, we rather want "can I destroy this class trivially", thus the std::is_trivially_destructible standard trait. Let's provide a builtin for it, and complain about asking whether a deleted destructor is trivial. Clang and MSVC also have these traits. PR c++/107600 gcc/cp/ChangeLog: * cp-trait.def (IS_DESTRUCTIBLE, IS_NOTHROW_DESTRUCTIBLE) (IS_TRIVIALLY_DESTRUCTIBLE): New. * constraint.cc (diagnose_trait_expr): Explain them. * method.cc (destructible_expr): New. (is_xible_helper): Use it. * semantics.cc (finish_trait_expr): Handle new traits. (trait_expr_value): Likewise. Complain about asking whether a deleted dtor is trivial. gcc/testsuite/ChangeLog: * g++.dg/ext/is_destructible1.C: New test. Diff: --- gcc/cp/constraint.cc | 9 +++++ gcc/cp/method.cc | 15 ++++++++ gcc/cp/semantics.cc | 20 ++++++++++ gcc/testsuite/g++.dg/ext/is_destructible1.C | 60 +++++++++++++++++++++++++++++ gcc/cp/cp-trait.def | 3 ++ 5 files changed, 107 insertions(+) diff --git a/gcc/cp/constraint.cc b/gcc/cp/constraint.cc index 44fb086c6306..90625707043f 100644 --- a/gcc/cp/constraint.cc +++ b/gcc/cp/constraint.cc @@ -3100,6 +3100,9 @@ diagnose_trait_expr (tree expr, tree args) case CPTK_IS_CONVERTIBLE: inform (loc, " %qT is not convertible from %qE", t2, t1); break; + case CPTK_IS_DESTRUCTIBLE: + inform (loc, " %qT is not destructible", t1); + break; case CPTK_IS_EMPTY: inform (loc, " %qT is not an empty class", t1); break; @@ -3145,6 +3148,9 @@ diagnose_trait_expr (tree expr, tree args) case CPTK_IS_NOTHROW_CONVERTIBLE: inform (loc, " %qT is not nothrow convertible from %qE", t2, t1); break; + case CPTK_IS_NOTHROW_DESTRUCTIBLE: + inform (loc, " %qT is not nothrow destructible", t1); + break; case CPTK_IS_NOTHROW_INVOCABLE: if (!t2) inform (loc, " %qT is not nothrow invocable", t1); @@ -3194,6 +3200,9 @@ diagnose_trait_expr (tree expr, tree args) case CPTK_IS_TRIVIALLY_COPYABLE: inform (loc, " %qT is not trivially copyable", t1); break; + case CPTK_IS_TRIVIALLY_DESTRUCTIBLE: + inform (loc, " %qT is not trivially destructible", t1); + break; case CPTK_IS_UNBOUNDED_ARRAY: inform (loc, " %qT is not an unbounded array", t1); break; diff --git a/gcc/cp/method.cc b/gcc/cp/method.cc index 092bae277875..3a675d9f8723 100644 --- a/gcc/cp/method.cc +++ b/gcc/cp/method.cc @@ -2330,6 +2330,19 @@ constructible_expr (tree to, tree from) return expr; } +/* Return declval<T>().~T() treated as an unevaluated operand. */ + +static tree +destructible_expr (tree to) +{ + cp_unevaluated cp_uneval_guard; + int flags = LOOKUP_NORMAL|LOOKUP_DESTRUCTOR; + to = build_trait_object (to); + tree r = build_delete (input_location, TREE_TYPE (to), to, + sfk_complete_destructor, flags, 0, tf_none); + return r; +} + /* Returns a tree iff TO is assignable (if CODE is MODIFY_EXPR) or constructible (otherwise) from FROM, which is a single type for assignment or a list of types for construction. */ @@ -2346,6 +2359,8 @@ is_xible_helper (enum tree_code code, tree to, tree from, bool trivial) tree expr; if (code == MODIFY_EXPR) expr = assignable_expr (to, from); + else if (code == BIT_NOT_EXPR) + expr = destructible_expr (to); else if (trivial && TREE_VEC_LENGTH (from) > 1 && cxx_dialect < cxx20) return error_mark_node; // only 0- and 1-argument ctors can be trivial diff --git a/gcc/cp/semantics.cc b/gcc/cp/semantics.cc index ef4a668a4e4d..241f2730878b 100644 --- a/gcc/cp/semantics.cc +++ b/gcc/cp/semantics.cc @@ -13235,6 +13235,14 @@ trait_expr_value (cp_trait_kind kind, tree type1, tree type2) case CPTK_HAS_TRIVIAL_DESTRUCTOR: type1 = strip_array_types (type1); + if (CLASS_TYPE_P (type1) && type_build_dtor_call (type1)) + { + deferring_access_check_sentinel dacs (dk_no_check); + tree fn = get_dtor (type1, tf_none); + if (!fn && !seen_error ()) + warning (0, "checking %qs for type %qT with a destructor that " + "cannot be called", "__has_trivial_destructor", type1); + } return (trivial_type_p (type1) || type_code1 == REFERENCE_TYPE || (CLASS_TYPE_P (type1) && TYPE_HAS_TRIVIAL_DESTRUCTOR (type1))); @@ -13290,6 +13298,9 @@ trait_expr_value (cp_trait_kind kind, tree type1, tree type2) case CPTK_IS_CONVERTIBLE: return is_convertible (type1, type2); + case CPTK_IS_DESTRUCTIBLE: + return is_xible (BIT_NOT_EXPR, type1, NULL_TREE); + case CPTK_IS_EMPTY: return NON_UNION_CLASS_TYPE_P (type1) && CLASSTYPE_EMPTY_P (type1); @@ -13329,6 +13340,9 @@ trait_expr_value (cp_trait_kind kind, tree type1, tree type2) case CPTK_IS_NOTHROW_CONVERTIBLE: return is_nothrow_convertible (type1, type2); + case CPTK_IS_NOTHROW_DESTRUCTIBLE: + return is_nothrow_xible (BIT_NOT_EXPR, type1, NULL_TREE); + case CPTK_IS_NOTHROW_INVOCABLE: return expr_noexcept_p (build_invoke (type1, type2, tf_none), tf_none); @@ -13371,6 +13385,9 @@ trait_expr_value (cp_trait_kind kind, tree type1, tree type2) case CPTK_IS_TRIVIALLY_COPYABLE: return trivially_copyable_p (type1); + case CPTK_IS_TRIVIALLY_DESTRUCTIBLE: + return is_trivially_xible (BIT_NOT_EXPR, type1, NULL_TREE); + case CPTK_IS_UNBOUNDED_ARRAY: return array_of_unknown_bound_p (type1); @@ -13543,6 +13560,9 @@ finish_trait_expr (location_t loc, cp_trait_kind kind, tree type1, tree type2) case CPTK_HAS_NOTHROW_COPY: case CPTK_HAS_TRIVIAL_COPY: case CPTK_HAS_TRIVIAL_DESTRUCTOR: + case CPTK_IS_DESTRUCTIBLE: + case CPTK_IS_NOTHROW_DESTRUCTIBLE: + case CPTK_IS_TRIVIALLY_DESTRUCTIBLE: if (!check_trait_type (type1)) return error_mark_node; break; diff --git a/gcc/testsuite/g++.dg/ext/is_destructible1.C b/gcc/testsuite/g++.dg/ext/is_destructible1.C new file mode 100644 index 000000000000..994c388263db --- /dev/null +++ b/gcc/testsuite/g++.dg/ext/is_destructible1.C @@ -0,0 +1,60 @@ +// PR c++/107600 +// { dg-do compile { target c++11 } } + +#define SA(X) static_assert ((X), #X) + +namespace N1 { + struct A { ~A() = delete; }; + + SA (!__is_destructible (A)); + SA (!__is_nothrow_destructible (A)); + SA (!__is_trivially_destructible (A)); + + bool b = __has_trivial_destructor (A); // { dg-message "has_trivial_destructor" } +} + +namespace N2 { + struct A { protected: ~A() = default; }; + + SA (!__is_destructible (A)); + SA (!__is_nothrow_destructible (A)); + SA (!__is_trivially_destructible (A)); + SA (__has_trivial_destructor (A)); +} + +namespace N3 { + struct A { ~A(); }; + + SA (__is_destructible (A)); + SA (__is_nothrow_destructible (A)); + SA (!__is_trivially_destructible (A)); + SA (!__has_trivial_destructor (A)); +} + +namespace N4 { + struct A { ~A() noexcept (false); }; + + SA (__is_destructible (A)); + SA (!__is_nothrow_destructible (A)); + SA (!__is_trivially_destructible (A)); + SA (!__has_trivial_destructor (A)); +} + +namespace N5 { + struct A { ~A() = default; }; + + SA (__is_destructible (A)); + SA (__is_nothrow_destructible (A)); + SA (__is_trivially_destructible (A)); + SA (__has_trivial_destructor (A)); +} + +namespace N6 { + struct A { }; + + SA (__is_destructible (A)); + SA (__is_nothrow_destructible (A)); + SA (__is_trivially_destructible (A)); + SA (__has_trivial_destructor (A)); +} + diff --git a/gcc/cp/cp-trait.def b/gcc/cp/cp-trait.def index 6aaca13c5e90..9c7380d7398e 100644 --- a/gcc/cp/cp-trait.def +++ b/gcc/cp/cp-trait.def @@ -71,6 +71,7 @@ DEFTRAIT_EXPR (IS_CLASS, "__is_class", 1) DEFTRAIT_EXPR (IS_CONST, "__is_const", 1) DEFTRAIT_EXPR (IS_CONSTRUCTIBLE, "__is_constructible", -1) DEFTRAIT_EXPR (IS_CONVERTIBLE, "__is_convertible", 2) +DEFTRAIT_EXPR (IS_DESTRUCTIBLE, "__is_destructible", 1) DEFTRAIT_EXPR (IS_EMPTY, "__is_empty", 1) DEFTRAIT_EXPR (IS_ENUM, "__is_enum", 1) DEFTRAIT_EXPR (IS_FINAL, "__is_final", 1) @@ -84,6 +85,7 @@ DEFTRAIT_EXPR (IS_MEMBER_POINTER, "__is_member_pointer", 1) DEFTRAIT_EXPR (IS_NOTHROW_ASSIGNABLE, "__is_nothrow_assignable", 2) DEFTRAIT_EXPR (IS_NOTHROW_CONSTRUCTIBLE, "__is_nothrow_constructible", -1) DEFTRAIT_EXPR (IS_NOTHROW_CONVERTIBLE, "__is_nothrow_convertible", 2) +DEFTRAIT_EXPR (IS_NOTHROW_DESTRUCTIBLE, "__is_nothrow_destructible", 1) DEFTRAIT_EXPR (IS_NOTHROW_INVOCABLE, "__is_nothrow_invocable", -1) DEFTRAIT_EXPR (IS_OBJECT, "__is_object", 1) DEFTRAIT_EXPR (IS_POINTER_INTERCONVERTIBLE_BASE_OF, "__is_pointer_interconvertible_base_of", 2) @@ -98,6 +100,7 @@ DEFTRAIT_EXPR (IS_TRIVIAL, "__is_trivial", 1) DEFTRAIT_EXPR (IS_TRIVIALLY_ASSIGNABLE, "__is_trivially_assignable", 2) DEFTRAIT_EXPR (IS_TRIVIALLY_CONSTRUCTIBLE, "__is_trivially_constructible", -1) DEFTRAIT_EXPR (IS_TRIVIALLY_COPYABLE, "__is_trivially_copyable", 1) +DEFTRAIT_EXPR (IS_TRIVIALLY_DESTRUCTIBLE, "__is_trivially_destructible", -1) DEFTRAIT_EXPR (IS_UNBOUNDED_ARRAY, "__is_unbounded_array", 1) DEFTRAIT_EXPR (IS_UNION, "__is_union", 1) DEFTRAIT_EXPR (IS_VIRTUAL_BASE_OF, "__builtin_is_virtual_base_of", 2)