]> git.ipfire.org Git - thirdparty/gcc.git/commitdiff
c++/reflection: undeduced auto, deferred noexcept [PR124628]
authorPatrick Palka <ppalka@redhat.com>
Thu, 14 May 2026 18:53:06 +0000 (14:53 -0400)
committerPatrick Palka <ppalka@redhat.com>
Thu, 14 May 2026 18:53:06 +0000 (14:53 -0400)
Various reflection queries reject functions (or variables) with an
undeduced return type.  But this assumes return type deduction has
already been attempted which is not the case if the function is a
specialization that has not yet been ODR-used or otherwise instantiated,
which we must do now.  Similarly a function can also have an deferred
noexcept-specification which we should also instantiate at this point.

Rather an inventing a new way to resolve the type of such a function
or variable for reflection purposes, I think we can just silently call
mark_used in an unevaluated context, which will behave similarly to
requires { &decl; }.  Since diagnostics (in the immediate context) get
suppressed, we'll gracefully handle deleted functions or those with
unsatisfied constraints, leaving it up to the caller to handle them.

PR c++/124628

gcc/cp/ChangeLog:

* reflect.cc (resolve_type_of_reflected_decl): New.
(get_reflection): Call resolve_type_of_reflected_decl instead
of mark_used.
(has_type): Call resolve_type_of_reflected_decl before
checking for an undeduced auto.
(eval_can_substitute): Likewise.  Also look through BASELINK.
(members_of_representable): Call resolve_type_of_reflected_decl
before checking for an undeduced auto.

gcc/testsuite/ChangeLog:

* g++.dg/reflect/can_substitute2.C: New test.
* g++.dg/reflect/members_of14.C: New test.
* g++.dg/reflect/substitute3.C: Adjust test so that f<int>'s
return type fails to get deduced.
* g++.dg/reflect/type_of3.C: Also test type_of of a templated
member function with deduced return type.

Reviewed-by: Jason Merrill <jason@redhat.com>
Reviewed-by: Marek Polacek <polacek@redhat.com>
gcc/cp/reflect.cc
gcc/testsuite/g++.dg/reflect/can_substitute2.C [new file with mode: 0644]
gcc/testsuite/g++.dg/reflect/members_of14.C [new file with mode: 0644]
gcc/testsuite/g++.dg/reflect/substitute3.C
gcc/testsuite/g++.dg/reflect/type_of3.C

index 1880a5ac7f5b9e3257cff62f1941326df66a1018..5459c8dfbe1800de8fa01aa4cb7411e59c2a5884 100644 (file)
@@ -74,6 +74,19 @@ init_reflection ()
   pop_namespace ();
 }
 
+/* Ensure the type of DECL is fully resolved by performing return
+   type deduction and deferred noexcept instantiation.  */
+
+static void
+resolve_type_of_reflected_decl (tree decl)
+{
+  /* Quietly calling mark_used in an unevaluated context will perform
+     all necessary checks and instantiations while suppressing constraint
+     unsatisfaction and deletedness diagnostics.  */
+  cp_unevaluated u;
+  mark_used (decl, tf_none);
+}
+
 /* Create a REFLECT_EXPR expression of kind KIND around T.  */
 
 static tree
@@ -210,8 +223,7 @@ get_reflection (location_t loc, tree t, reflect_kind kind/*=REFLECT_UNDEF*/)
       t = resolve_nondeduced_context_or_error (t, tf_warning_or_error);
       /* The argument could have a deduced return type, so we need to
         instantiate it now to find out its type.  */
-      if (!mark_used (t))
-       return error_mark_node;
+      resolve_type_of_reflected_decl (t);
       /* Avoid -Wunused-but-set* warnings when a variable or parameter
         is just set and reflected.  */
       if (VAR_P (t) || TREE_CODE (t) == PARM_DECL)
@@ -2542,6 +2554,7 @@ has_type (tree r, reflect_kind kind)
     {
       if (DECL_CONSTRUCTOR_P (r) || DECL_DESTRUCTOR_P (r))
        return false;
+      resolve_type_of_reflected_decl (r);
       if (undeduced_auto_decl (r))
        return false;
       return true;
@@ -5541,6 +5554,8 @@ eval_can_substitute (location_t loc, const constexpr_ctx *ctx,
       if (fn == error_mark_node)
        return boolean_false_node;
       fn = resolve_nondeduced_context_or_error (fn, tf_none);
+      fn = MAYBE_BASELINK_FUNCTIONS (fn);
+      resolve_type_of_reflected_decl (fn);
       if (fn == error_mark_node || undeduced_auto_decl (fn))
        return boolean_false_node;
       return boolean_true_node;
@@ -6768,8 +6783,12 @@ members_of_representable_p (tree c, tree r)
          || TREE_CODE (r) == FIELD_DECL
          || TREE_CODE (r) == NAMESPACE_DECL)
        return true;
-      if (VAR_OR_FUNCTION_DECL_P (r) && !undeduced_auto_decl (r))
-       return true;
+      if (VAR_OR_FUNCTION_DECL_P (r))
+       {
+         resolve_type_of_reflected_decl (r);
+         if (!undeduced_auto_decl (r))
+           return true;
+       }
     }
   return false;
 }
diff --git a/gcc/testsuite/g++.dg/reflect/can_substitute2.C b/gcc/testsuite/g++.dg/reflect/can_substitute2.C
new file mode 100644 (file)
index 0000000..6628a1b
--- /dev/null
@@ -0,0 +1,19 @@
+// [meta.reflection.substitute] Example 1
+// { dg-do compile { target c++26 } }
+// { dg-additional-options "-freflection" }
+
+#include <meta>
+
+template<class T>
+auto fn1();
+
+static_assert(!can_substitute(^^fn1, {^^int}));
+constexpr auto r1 = substitute(^^fn1, {^^int}); // { dg-error "can_substitute returned false" }
+
+template<class T>
+auto fn2() {
+  static_assert(false); // { dg-error "assert" }
+  return T{};
+}
+
+constexpr bool r2 = can_substitute(^^fn2, {^^int}); // { dg-message "required from here" }
diff --git a/gcc/testsuite/g++.dg/reflect/members_of14.C b/gcc/testsuite/g++.dg/reflect/members_of14.C
new file mode 100644 (file)
index 0000000..6a96906
--- /dev/null
@@ -0,0 +1,29 @@
+// PR c++/124628
+// { dg-do compile { target c++26 } }
+// { dg-additional-options "-freflection" }
+
+#include <meta>
+
+template<class T> void f();
+
+template<class T>
+struct A {
+  void g() noexcept(noexcept(T{}));
+  auto h() { return T{}; }
+  static inline auto m = T{};
+  // A& operator=(const A&) noexcept;
+  // A& operator=(A&&) noexcept;
+};
+
+int main() {
+  constexpr auto ac = std::meta::access_context::current();
+  template for (constexpr auto mem : define_static_array(members_of(^^A<int>, ac)))
+    if constexpr (!is_constructor(mem) && !is_destructor(mem))
+      f<typename [:type_of(mem):]>();
+}
+
+// { dg-final { scan-assembler _Z1fIDoFvvEEvv } } void f<void () noexcept>()
+// { dg-final { scan-assembler _Z1fIFivEEvv } } void f<int ()>()
+// { dg-final { scan-assembler _Z1fIiEvv } } void f<int>()
+// { dg-final { scan-assembler _Z1fIDoFR1AIiERKS1_EEvv } } void f<A<int>& (A<int> const&) noexcept>()
+// { dg-final { scan-assembler _Z1fIDoFR1AIiEOS1_EEvv } } void f<A<int>& (A<int>&&) noexcept>()
index ff1b1aeaf4da4059b42c7295beaa1f5f42a9ad9a..19329a80efea9db10e10718080587c5a0b32e8c2 100644 (file)
@@ -7,9 +7,7 @@
 
 template<typename>
 auto
-f ()
-{
-}
+f ();
 
 consteval bool
 g ()
index ba28c4f5980811b1b14fe3e483948a83ecff23e6..b739851da2e02903965a5e1ebee1fa65ad1ab40a 100644 (file)
@@ -10,4 +10,10 @@ struct S {
 };
 int h() { return 0; }
 
+template<class T>
+struct ST {
+    auto g() { return T{}; }
+};
+
 static_assert(type_of(^^S::g) == type_of(^^h));
+static_assert(type_of(^^ST<int>::g) == type_of(^^h));