On Wed, 23 Jul 2025, Patrick Palka wrote:
> On Wed, 23 Jul 2025, Jason Merrill wrote:
>
> > On 7/23/25 3:46 PM, Patrick Palka wrote:
> > > As a follow-up to r16-2448-g7590c14b53a762, this patch attempts to teach
> > > build_min_non_dep_op_overload how to rebuild all rewritten comparison
> > > operators, not just != -> == ones, so that we don't incorrectly repeat
> > > the unqualified name lookup at instantiation time.
> >
> > Talking about mangling earlier made me wonder how we were handling
> > non-dependent operator expressions, and indeed it seems we get it wrong
> > since
> > GCC 6:
> >
> > struct A { };
> > A operator+(A,A);
> > template <class T>
> > void f(decltype(T(),A()+A())) { }
> > int main()
> > {
> > f<int>(A()); // oops, mangles as operator+(A(),A()) instead of A()+A()
> > }
> >
> > while clang and EDG corretly use the latter mangling.
> >
> > With the current code I would think we could fix this by handling
> > CALL_EXPR_OPERATOR_SYNTAX in mangle.cc, but your patch (and indeed the
> > earlier
> > one) would further obscure the original syntax.
>
> Does this mean it's also incorrect to mangle the ordinary non-dependent f(0)
> call in:
>
> template<class T> void f(T);
>
> template<class T> decltype(T(),f(0)) g();
>
> int main() {
> g<int>();
> }
>
> as f<int>(0) i.e. with an explicit template argument list even though it was
> written without one? Clang mangles it as f<int>(0) too, not sure about EDG.
> This changed in GCC 12 with the non-dependent overload set pruning
> optimization.
>
> And does this have any declaration matching implications? Say for
>
> struct A { };
>
> template<class T> int operator+(A,T);
> template<class T> decltype(T(),A()+A()) f();
>
> A operator+(A,A);
> template<class T> decltype(T(),A()+A()) f();
>
> int main() {
> f<int>();
> }
>
> should we still reject the f<int>() call as ambiguous, or treat the second
> declaration as a redeclaration (since they have the same mangling?) This
> seems
> related to CWG1321 but for non-dependent calls.
>
> >
> > > While working on this I noticed we'll seemingly never create a rewritten
> > > operator expression that is in terms of a built-in operator, since we
> > > could have used a built-in operator directly in the first place, which
> > > simplifies things. I think this also means the extract_call_expr
> > > handling of rewritten operators is wrong since it inspects for LT_EXPR,
> > > SPACESHIP_EXPR etc directly, so this patch just removes it in passing.
> >
> > That code is not about rewriting in terms of a built-in operator, it was to
> > look through the operations added by the rewriting, e.g. TRUTH_NOT_EXPR for
> > operator!= to !(operator==) to find the actual call to the operator
> > underneath.
>
> The TRUTH_NOT_EXPR case seems fine, but AFAICT the LT_EXPR, GT_EXPR etc cases
> are dead code because we'll never have an LT/GT/etc_EXPR of an operator<=>
> call,
> since operator<=> must return std::strong/weak/partial_ordering which are
> class
> types, and so 0 < (x <=> y) must always resolve to a user-defined operator<
> etc.
>
> Oh wait, that'll only be true after the rest of the patch is applied...
> otherwise
> non-dependent templated rewritten operator expressions will indeed contain
> LT/GT/etc_EXPR.
>
> >
> > It does look like that's unnecessary now because build_new_op calls
> > extract_call_expr before adding those decorations, so I don't object to
> > removing it, but please make that a separate patch.
>
> Sounds good.
Here's v3 which omits the extract_call_expr removal.
I'm not sure how or if we want to address the mangling concern.
With this patch we'll now at least our non-dependent operator
expression mangling will be consistent :)
-- >8 --
Subject: [PATCH] c++: more name lookup for non-dep rewritten cmp ops
gcc/cp/ChangeLog:
* call.cc (build_new_op): If the selected candidate is
rewritten, communicate the LOOKUP_REWRITTEN/REVERSED flags to
the caller via the *overload out-parameter, and stop clearing
*overload in that case.
* tree.cc (build_min_non_dep_op_overload): Handle rebuilding all
C++20 rewritten comparison operator expressions.
gcc/testsuite/ChangeLog:
* g++.dg/lookup/operator-8.C: Remove XFAILs and properly
suppress all -Wunused-result warnings.
---
gcc/cp/call.cc | 16 +++--
gcc/cp/tree.cc | 85 ++++++++++++++++++++----
gcc/testsuite/g++.dg/lookup/operator-8.C | 16 +++--
3 files changed, 92 insertions(+), 25 deletions(-)
diff --git a/gcc/cp/call.cc b/gcc/cp/call.cc
index c925dd18ab41..825d171fdeb0 100644
--- a/gcc/cp/call.cc
+++ b/gcc/cp/call.cc
@@ -7486,7 +7486,16 @@ build_new_op (const op_location_t &loc, enum tree_code
code, int flags,
else if (TREE_CODE (cand->fn) == FUNCTION_DECL)
{
if (overload)
- *overload = cand->fn;
+ {
+ if (cand->rewritten ())
+ /* build_min_non_dep_op_overload needs to know whether the
+ candidate is rewritten/reversed. */
+ *overload = build_tree_list (build_int_cst (integer_type_node,
+ cand->flags),
+ cand->fn);
+ else
+ *overload = cand->fn;
+ }
if (resolve_args (arglist, complain) == NULL)
result = error_mark_node;
@@ -7535,11 +7544,6 @@ build_new_op (const op_location_t &loc, enum tree_code
code, int flags,
/* If this was a C++20 rewritten comparison, adjust the result. */
if (cand->rewritten ())
{
- /* FIXME build_min_non_dep_op_overload can't handle rewrites. */
- if (code == NE_EXPR && !cand->reversed ())
- /* It can handle != rewritten to == though. */;
- else if (overload)
- *overload = NULL_TREE;
switch (code)
{
case EQ_EXPR:
diff --git a/gcc/cp/tree.cc b/gcc/cp/tree.cc
index c260efb7f6ba..50659c2de8be 100644
--- a/gcc/cp/tree.cc
+++ b/gcc/cp/tree.cc
@@ -3696,7 +3696,58 @@ build_min_non_dep_op_overload (enum tree_code op,
int nargs, expected_nargs;
tree fn, call, obj = NULL_TREE;
- bool negated = (TREE_CODE (non_dep) == TRUTH_NOT_EXPR);
+ releasing_vec args;
+ va_start (p, overload);
+
+ bool negated = false, rewritten = false, reversed = false;
+ if (cxx_dialect >= cxx20 && TREE_CODE (overload) == TREE_LIST)
+ {
+ /* Handle rebuilding a C++20 rewritten comparison operator expression,
+ e.g. !(x == y), y <=> x, (x <=> y) @ 0, etc, that resolved to a call
+ to a user-defined operator<=>/==. */
+ gcc_checking_assert (TREE_CODE_CLASS (op) == tcc_comparison
+ || op == SPACESHIP_EXPR);
+ int flags = TREE_INT_CST_LOW (TREE_PURPOSE (overload));
+ if (TREE_CODE (non_dep) == TRUTH_NOT_EXPR)
+ {
+ negated = true;
+ non_dep = TREE_OPERAND (non_dep, 0);
+ }
+ if (flags & LOOKUP_REWRITTEN)
+ rewritten = true;
+ if (flags & LOOKUP_REVERSED)
+ reversed = true;
+ if (rewritten
+ && DECL_OVERLOADED_OPERATOR_IS (TREE_VALUE (overload),
+ SPACESHIP_EXPR))
+ {
+ /* Handle (x <=> y) @ 0 and 0 @ (y <=> x) by recursing to first
+ rebuild the <=>. Note that both OVERLOAD and the provided
arguments
+ in this case correspond to the selected operator<=>. */
+
+ tree spaceship_non_dep = CALL_EXPR_ARG (non_dep, reversed ? 1 : 0);
+ gcc_checking_assert (TREE_CODE (spaceship_non_dep) == CALL_EXPR);
+ tree spaceship_op0 = va_arg (p, tree);
+ tree spaceship_op1 = va_arg (p, tree);
+ if (reversed)
+ std::swap (spaceship_op0, spaceship_op1);
+
+ /* Push the correct arguments for the operator OP expression, and set
+ OVERLOAD appropriately. */
+ tree op0 = build_min_non_dep_op_overload (SPACESHIP_EXPR,
+ spaceship_non_dep,
+ TREE_VALUE (overload),
+ spaceship_op0,
+ spaceship_op1);
+ tree op1 = CALL_EXPR_ARG (non_dep, reversed ? 0 : 1);
+ gcc_checking_assert (integer_zerop (op1));
+ vec_safe_push (args, op0);
+ vec_safe_push (args, op1);
+ overload = CALL_EXPR_FN (non_dep);
+ }
+ else
+ overload = TREE_VALUE (overload);
+ }
non_dep = extract_call_expr (non_dep);
nargs = call_expr_nargs (non_dep);
@@ -3717,32 +3768,40 @@ build_min_non_dep_op_overload (enum tree_code op,
expected_nargs += 1;
gcc_assert (nargs == expected_nargs);
- releasing_vec args;
- va_start (p, overload);
-
if (!DECL_OBJECT_MEMBER_FUNCTION_P (overload))
{
fn = overload;
- if (op == ARRAY_REF)
- obj = va_arg (p, tree);
- for (int i = 0; i < nargs; i++)
+ if (vec_safe_length (args) != 0)
+ /* The correct arguments were already pushed above. */
+ gcc_checking_assert (rewritten);
+ else
{
- tree arg = va_arg (p, tree);
- vec_safe_push (args, arg);
+ if (op == ARRAY_REF)
+ obj = va_arg (p, tree);
+ for (int i = 0; i < nargs; i++)
+ {
+ tree arg = va_arg (p, tree);
+ vec_safe_push (args, arg);
+ }
}
+ if (reversed)
+ std::swap ((*args)[0], (*args)[1]);
}
else
{
+ gcc_checking_assert (vec_safe_length (args) == 0);
tree object = va_arg (p, tree);
- tree binfo = TYPE_BINFO (TREE_TYPE (object));
- tree method = build_baselink (binfo, binfo, overload, NULL_TREE);
- fn = build_min (COMPONENT_REF, TREE_TYPE (overload),
- object, method, NULL_TREE);
for (int i = 0; i < nargs; i++)
{
tree arg = va_arg (p, tree);
vec_safe_push (args, arg);
}
+ if (reversed)
+ std::swap (object, (*args)[0]);
+ tree binfo = TYPE_BINFO (TREE_TYPE (object));
+ tree method = build_baselink (binfo, binfo, overload, NULL_TREE);
+ fn = build_min (COMPONENT_REF, TREE_TYPE (overload),
+ object, method, NULL_TREE);
}
va_end (p);
diff --git a/gcc/testsuite/g++.dg/lookup/operator-8.C
b/gcc/testsuite/g++.dg/lookup/operator-8.C
index 7fe6a57061bd..32d432dd8432 100644
--- a/gcc/testsuite/g++.dg/lookup/operator-8.C
+++ b/gcc/testsuite/g++.dg/lookup/operator-8.C
@@ -16,12 +16,16 @@ struct A {
template<class T>
void f() {
A a;
- (void)(a != 0); // We only handle this simple case, after PR121179
- (void)(0 != a); // { dg-bogus "deleted" "" { xfail *-*-* } }
- (void)(a < 0, 0 < a); // { dg-bogus "deleted" "" { xfail *-*-* } }
- (void)(a <= 0, 0 <= a); // { dg-bogus "deleted" "" { xfail *-*-* } }
- (void)(a > 0, 0 > a); // { dg-bogus "deleted" "" { xfail *-*-* } }
- (void)(a >= 0, 0 >= a); // { dg-bogus "deleted" "" { xfail *-*-* } }
+ (void)(a != 0);
+ (void)(0 != a);
+ (void)(a < 0);
+ (void)(0 < a);
+ (void)(a <= 0);
+ (void)(0 <= a);
+ (void)(a > 0);
+ (void)(0 > a);
+ (void)(a >= 0);
+ (void)(0 >= a);
}
// These later-declared namespace-scope overloads shouldn't be considered
--
2.50.1.319.g90c0775e97