On 6/29/26 12:16 PM, Jakub Jelinek wrote:
On Mon, Jun 29, 2026 at 11:43:44AM -0400, Jason Merrill wrote:
https://eel.is/c++draft/class.prop#8.2 says that implicit lifetime
class shall have at least one trivial eligible constructor.
We currently walk all ctors and if it is default/copy/move ctor
which is trivial and not deleted, return true, but as the following testcase
shows, we should check also for unsatisfied constraints.
I wonder about adding an eligible_special_function_p function, though it
couldn't check the "more constrained" condition and would need to refer to
add_method's handling of that.
Let's also add a quote comment here of the section you link to above.
So like this?
OK, thanks.
2026-06-29 Jakub Jelinek <[email protected]>
PR c++/126007
* tree.cc (eligible_special_member_function_p): New function.
(implicit_lifetime_type_p): Use true instead of 1 in function comment.
Add some further comments. Use eligible_special_member_function_p
instead of !DECL_DELETED_FN.
* g++.dg/ext/is_implicit_lifetime3.C: New test.
--- gcc/cp/tree.cc.jj 2026-06-25 10:03:50.818436378 +0200
+++ gcc/cp/tree.cc 2026-06-29 18:12:56.381781909 +0200
@@ -4919,7 +4919,25 @@ trivially_copy_constructible_p (tree t)
return is_trivially_xible (INIT_EXPR, t, arg);
}
-/* Returns 1 iff type T is an implicit-lifetime type, as defined in
+/* Returns true iff FN (which is a special member function) is
+ an elifible special member function, as defined in [special]/6.
+ The "no special member function of the same kind whose associated
+ constraints, if any, are satisfied is more constrained" condition
+ is not checked, this is checked during add_method instead. */
+
+static bool
+eligible_special_member_function_p (tree fn)
+{
+ /* The function is not deleted. */
+ if (DECL_DELETED_FN (fn))
+ return false;
+ /* The associated constraints, if any, are satisfied. */
+ if (!constraints_satisfied_p (fn))
+ return false;
+ return true;
+}
+
+/* Returns true iff type T is an implicit-lifetime type, as defined in
[basic.types.general] and [class.prop]. */
bool
@@ -4934,6 +4952,7 @@ implicit_lifetime_type_p (tree t)
if (!CLASS_TYPE_P (t))
return false;
t = TYPE_MAIN_VARIANT (t);
+ /* It is an aggregate whose destructor is not user-provided. */
if (CP_AGGREGATE_TYPE_P (t)
&& (!CLASSTYPE_DESTRUCTOR (t)
|| !user_provided_p (CLASSTYPE_DESTRUCTOR (t))))
@@ -4946,6 +4965,8 @@ implicit_lifetime_type_p (tree t)
return false;
if (CLASSTYPE_LAZY_DESTRUCTOR (t))
lazily_declare_fn (sfk_destructor, t);
+ /* It has at least one trivial eligible constructor and a trivial,
+ non-deleted destructor. */
tree fn = CLASSTYPE_DESTRUCTOR (t);
if (!fn || DECL_DELETED_FN (fn))
return false;
@@ -4955,7 +4976,7 @@ implicit_lifetime_type_p (tree t)
fn = *iter;
if ((default_ctor_p (fn) || copy_fn_p (fn) || move_fn_p (fn))
&& trivial_fn_p (fn)
- && !DECL_DELETED_FN (fn))
+ && eligible_special_member_function_p (fn))
return true;
}
return false;
--- gcc/testsuite/g++.dg/ext/is_implicit_lifetime3.C.jj 2026-06-27
11:49:33.102692105 +0200
+++ gcc/testsuite/g++.dg/ext/is_implicit_lifetime3.C 2026-06-27
11:50:38.106905466 +0200
@@ -0,0 +1,11 @@
+// PR c++/126007
+// { dg-do compile { target c++20 } }
+
+template <bool B>
+struct S {
+ S () requires B = default;
+ S (const S &) {}
+};
+
+static_assert (!__builtin_is_implicit_lifetime (S<false>));
+static_assert (__builtin_is_implicit_lifetime (S<true>));
Jakub