On Thu, May 14, 2026 at 12:28:36PM -0400, Patrick Palka wrote:
> Tested on x86_64-pc-linux-gnu, does this look OK for trunk/16?
>
> -- >8 --
>
> 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.
> Similarly we can also have an uninstantiated noexcept 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, making it behave similarly to
> requires { &decl; }. Since diagnostics (in the immediate context) will
> be suppressed it means 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: Extend test to check for
Something seems to be missing here.
> ---
> gcc/cp/reflect.cc | 32 ++++++++++++++++---
> .../g++.dg/reflect/can_substitute2.C | 19 +++++++++++
> gcc/testsuite/g++.dg/reflect/members_of14.C | 29 +++++++++++++++++
> gcc/testsuite/g++.dg/reflect/substitute3.C | 4 +--
> gcc/testsuite/g++.dg/reflect/type_of3.C | 6 ++++
> 5 files changed, 82 insertions(+), 8 deletions(-)
> create mode 100644 gcc/testsuite/g++.dg/reflect/can_substitute2.C
> create mode 100644 gcc/testsuite/g++.dg/reflect/members_of14.C
>
> diff --git a/gcc/cp/reflect.cc b/gcc/cp/reflect.cc
> index ad4c77fab3eb..96450a7ebb58 100644
> --- a/gcc/cp/reflect.cc
> +++ b/gcc/cp/reflect.cc
> @@ -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)
> +{
> + cp_unevaluated u;
> + /* Quietly calling mark_used in an unevaluated context will perform
> + all necessary checks and instantiations while suppressing constraint
> + unsatisfaction and deletedness diagnostics. */
> + mark_used (decl, tf_none);
Are you sure we can ignore the result of mark_used here? E.g. here...
> +}
> +
> /* 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;
... we'd return but now we don't. I would think resolve_type_of_reflected_decl
should return what mark_used returned.
> + 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)
> @@ -2533,6 +2545,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;
> @@ -5532,7 +5545,12 @@ 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);
> - if (fn == error_mark_node || undeduced_auto_decl (fn))
> + if (fn == error_mark_node)
> + return boolean_false_node;
> + if (BASELINK_P (fn))
> + fn = BASELINK_FUNCTIONS (fn);
This can be simplified to MAYBE_BASELINK_FUNCTIONS.
> + resolve_type_of_reflected_decl (fn);
> + if (undeduced_auto_decl (fn))
> return boolean_false_node;
> return boolean_true_node;
> }
> @@ -6759,8 +6777,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
> index 000000000000..6628a1b72060
> --- /dev/null
> +++ b/gcc/testsuite/g++.dg/reflect/can_substitute2.C
> @@ -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
> index 000000000000..6a969063aa4b
> --- /dev/null
> +++ b/gcc/testsuite/g++.dg/reflect/members_of14.C
> @@ -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>()
> diff --git a/gcc/testsuite/g++.dg/reflect/substitute3.C
> b/gcc/testsuite/g++.dg/reflect/substitute3.C
> index ff1b1aeaf4da..19329a80efea 100644
> --- a/gcc/testsuite/g++.dg/reflect/substitute3.C
> +++ b/gcc/testsuite/g++.dg/reflect/substitute3.C
> @@ -7,9 +7,7 @@
>
> template<typename>
> auto
> -f ()
> -{
> -}
> +f ();
>
> consteval bool
> g ()
> diff --git a/gcc/testsuite/g++.dg/reflect/type_of3.C
> b/gcc/testsuite/g++.dg/reflect/type_of3.C
> index ba28c4f59808..b739851da2e0 100644
> --- a/gcc/testsuite/g++.dg/reflect/type_of3.C
> +++ b/gcc/testsuite/g++.dg/reflect/type_of3.C
> @@ -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));
> --
> 2.54.0.rc1.54.g60f07c4f5c
>
Marek