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