Re: [PATCH] c++: resolvedness of resolve_nondeduced_context result [PR126406]

Patrick Palka <[email protected]>
Newsgroups gmane.comp.gcc.patches
Message-ID <4217712c-0a91-dee0-17c1-1eb5d9290d96@idea>
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);
> 
>
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.