On Thu, 14 May 2026, Marek Polacek wrote:

> 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.

Oops, will fix.

> 
> > ---
> >  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.

The problem is that mark_used returns false when the function cannot be
ODR-used, not only when its type is unresolved.  In particular it
returns false for a deleted function, but a deleted function is not a
problem for most callers, in which case the result of mark_used is
potentially misleading and checking it is at best an optimization.  A
'true' result is great and means the function's type is fully resolved,
but a 'false' result means either the function's type is not fully
resolved, or it's deleted, or its constraints are unsatisfied, etc, so
the caller must do further checks anyway.

For the get_reflection call site in particular, the current mark_used
check means we reject:

  template<class T> void g() = delete;
  constexpr auto r = ^^g<int>; // currently error: use of deleted function

We need to ignore the result of mark_used in order to accept this.  This
is part of the unresolved CWG3166 so maybe this will end up being invalid,
but for now it seems like something we should try to support, given that
we already support (perhaps accidentally)

  void f() = delete;
  ^^f;

since this doesn't take the mark_used code path in get_reflection.

> 
> > +      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.

Done.

> 
> > +      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
> 
> 

Reply via email to