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);
>
>