On Thu, 30 Jul 2026, Jason Merrill wrote:

> On 7/29/26 3:40 PM, Patrick Palka wrote:
> > Bootstrapped and regtested on x86_64-pc-linux-gnu, does this look
> > OK for trunk/16?
> > 
> > -- >8 --
> > 
> > In r16-5967-gbae0ed69e1862a we removed the mark_used logic from
> > resolve_nondeduced_context under the rationale that it should be the
> > callers' responsiblity to mark it.  Resolving a template-id overload
> > shouldn't constitute an ODR-used on its own,
> 
> Hmm, https://eel.is/c++draft/basic#def.odr-4.1 (plus p8) seems to say it does,
> as it's the selected member of an overload set in a potentially-evaluated
> context.

I see, so resolve_nondeduced_context should call mark_used after all?
And should it also call mark_used on non-template-id overloads?

> 
> For r16-5967, I don't see why the patch made a difference to the testcase; in
> convert_to_void resolve_nondeduced_function is followed by
> mark_single_function, so we should return error_mark_node regardless of where
> we first call mark_used.

The problem is that convert_to_void didn't propagate error_mark_node
result from mark_used, instead it just returned void_node.  So we could
have more simply fixed that PR by just propagating error_mark_node
but I was under the impression that resolve_nondeduced_function shouldn't
call mark_used at all which incidentally fixes the PR at a higher level
(resolve_nondeduced_function can no longer return error_mark_node).

> 
> > and callers would have to
> > call mark_used anyway to uniformly handle all overloads, including
> > non-template-id ones.
> 
> Yes, so r16-5967 is consistent with the comment to mark_single_function.

But I thought the mentioned [basic.def.odr]/4.1 and /8 suggests
resolve_nondeduced_function could safely call mark_single_function?

> 
> > Removing this logic however now means that resolve_nondeduced_context
> > could return a specialization whose type is not yet fully resolved
> > (i.e. has an uninstantiated noexcept or undeduced return type), and
> > callers that immediately inspect TREE_TYPE of the result (such as
> > standard_conversion and build_conditional_expr) now misbehave.
> 
> I think the important case is standard_conversion; at that point we likely
> haven't finished overload resolution, so the selection is still tentative and
> so odr-use would be premature.  Though that's why we have tf_conv.  But then,
> it looks like mark_used wrongly misses resolving auto if tf_conv.

Yeah, I'm not immediately sure whether tf_conv users expected it resolve
auto and instaniate noexcept etc.

> 
> In build_conditional_expr it looks like we no longer have anything that will
> mark_used the resolved operand, i.e. we're missing a call to
> mark_single_function.  Does this lead to missed instantiation if the return
> type isn't deduced?

It causes us to reject the newly added cond2a with:

  cond2a.C:17:29: error: use of ‘decltype ((b ?  f<int> : <throw-expression>)) 
g() [with T = int; decltype ((b ?  f<int> : <throw-expression>)) = void (&)()]’ 
before deduction of ‘auto’

And for

    bool b;

    template < class T > void f ()
    {
      __builtin_printf("Hello World!\n");
    }

    template < class T > void g ()
    {
      auto p = !b ? f<int> : throw 0;
      p();
    }

    int main() {
      g<int>();
    }

it seems we eventually call mark_single_function on f<int> from
cp_build_addr_expr_1, so no missed instantiation in that case at least.

> 
> The function comment for resolve_nondeduced_context should mention that most
> uses also want mark_single_function.

They should _eventually_ call mark_single_function, not necessarily
immediately after, right?

For sake of backporting we should we then just revert r16-5967 and
instead check error_mark_node result from mark_single_function
in convert_to_void?

> 
> > We could adjust callers to call mark_used before inspecting TREE_TYPE,
> > but I think it's reasonable to expect that the (successful) result of
> > resolve_nondeduced_context always has a resolved function type.  To
> > that end this patch restores the mark_used call, but ensures it's
> > always done in an unevaluated context and quietly so that its only
> > effect is to resolve the function type.  (Note we do this same technique
> > in the Reflection implementation, see resolve_type_of_reflected_decl).
> > 
> >     PR c++/126406
> >     PR c++/119343
> > 
> > gcc/cp/ChangeLog:
> > 
> >     * pt.cc (resolve_nondeduced_context): Restore mark_used call
> >     but always do it quietly and in an unevaluated context.
> > 
> > gcc/testsuite/ChangeLog:
> > 
> >     * g++.dg/cpp0x/cond2a.C: New test.
> >     * g++.dg/cpp1y/auto-fn67.C: New test.
> >     * g++.dg/cpp1z/noexcept-type29.C: New test.
> > ---
> >   gcc/cp/pt.cc                                 |  5 +++++
> >   gcc/testsuite/g++.dg/cpp0x/cond2a.C          | 17 +++++++++++++++++
> >   gcc/testsuite/g++.dg/cpp1y/auto-fn67.C       | 11 +++++++++++
> >   gcc/testsuite/g++.dg/cpp1z/noexcept-type29.C | 11 +++++++++++
> >   4 files changed, 44 insertions(+)
> >   create mode 100644 gcc/testsuite/g++.dg/cpp0x/cond2a.C
> >   create mode 100644 gcc/testsuite/g++.dg/cpp1y/auto-fn67.C
> >   create mode 100644 gcc/testsuite/g++.dg/cpp1z/noexcept-type29.C
> > 
> > diff --git a/gcc/cp/pt.cc b/gcc/cp/pt.cc
> > index 6a081b838e06..3605c2ea8b93 100644
> > --- a/gcc/cp/pt.cc
> > +++ b/gcc/cp/pt.cc
> > @@ -25653,6 +25653,11 @@ resolve_nondeduced_context (tree orig_expr,
> > tsubst_flags_t complain)
> >     }
> >         if (good == 1)
> >     {
> > +     /* Ensure the selected function's type is fully resolved.  */
> > +     ++cp_unevaluated_operand;
> > +     mark_used (goodfn, tf_none);
> > +     --cp_unevaluated_operand;
> > +
> >       expr = goodfn;
> >       if (baselink)
> >         expr = build_baselink (BASELINK_BINFO (baselink),
> > diff --git a/gcc/testsuite/g++.dg/cpp0x/cond2a.C
> > b/gcc/testsuite/g++.dg/cpp0x/cond2a.C
> > new file mode 100644
> > index 000000000000..a42e198291b2
> > --- /dev/null
> > +++ b/gcc/testsuite/g++.dg/cpp0x/cond2a.C
> > @@ -0,0 +1,17 @@
> > +// PR c++/126406
> > +// { dg-do compile { target c++14 } }
> > +// A version of cond2.C where f has a deduced return type
> > +// and g is instantiated.
> > +
> > +bool b;
> > +
> > +template < class T > auto f ()
> > +{
> > +}
> > +
> > +template < class T > auto g () -> decltype (b ? f < int > : throw 0)
> > +{
> > +  return b ? f<int> : throw 0;
> > +}
> > +
> > +using type = decltype(g<int>());
> > diff --git a/gcc/testsuite/g++.dg/cpp1y/auto-fn67.C
> > b/gcc/testsuite/g++.dg/cpp1y/auto-fn67.C
> > new file mode 100644
> > index 000000000000..20ff86459c3d
> > --- /dev/null
> > +++ b/gcc/testsuite/g++.dg/cpp1y/auto-fn67.C
> > @@ -0,0 +1,11 @@
> > +// PR c++/126406
> > +// { dg-do compile { target c++14 } }
> > +
> > +template<class T> auto g(T) { }
> > +static_assert(g<int>);
> > +
> > +template<class T>
> > +struct B {
> > +  static auto g(T) { }
> > +};
> > +static_assert(B<int>::g);
> > diff --git a/gcc/testsuite/g++.dg/cpp1z/noexcept-type29.C
> > b/gcc/testsuite/g++.dg/cpp1z/noexcept-type29.C
> > new file mode 100644
> > index 000000000000..c620b180fc83
> > --- /dev/null
> > +++ b/gcc/testsuite/g++.dg/cpp1z/noexcept-type29.C
> > @@ -0,0 +1,11 @@
> > +// PR c++/126406
> > +// { dg-do compile { target c++11 } }
> > +
> > +template<class T> void f(T) noexcept(noexcept(T())) { }
> > +static_assert(f<int>);
> > +
> > +template<class T>
> > +struct A {
> > +  static void f(T) noexcept(noexcept(T())) { }
> > +};
> > +static_assert(A<int>::f);
> 
> 

Reply via email to