Skip to content

Commit 460ff58

Browse files
authored
[clang] Reapply Handle templated operators with reversed arguments (#72213)
Re-applies #69595 with extra [diff](79181ef) ### New changes Further relax ambiguities with a warning for member operators of a template class (primary templates of such ops do not match). Eg: ```cpp template <class T> struct S { template <typename OtherT> bool operator==(const OtherT &rhs); }; struct A : S<int> {}; struct B : S<bool> {}; bool x = A{} == B{}; // accepted with a warning. ``` This is important for making llvm build using previous clang versions in C++20 mode (eg: this makes the commit e558be5 keep working with a warning instead of an error). ### Description from #69595 #68999 correctly computed conversion sequence for reversed args to a template operator. This was a breaking change as code, previously accepted in C++17, starts to break in C++20. Example: ```cpp struct P {}; template<class S> bool operator==(const P&, const S &); struct A : public P {}; struct B : public P {}; bool check(A a, B b) { return a == b; } // This is now ambiguous in C++20. ``` In order to minimise widespread breakages, as a clang extension, we had previously accepted such ambiguities with a warning (`-Wambiguous-reversed-operator`) for non-template operators. Due to the same reasons, we extend this relaxation for template operators. Fixes #53954
1 parent 4556813 commit 460ff58

File tree

3 files changed

+175
-12
lines changed

3 files changed

+175
-12
lines changed

clang/docs/ReleaseNotes.rst

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,29 @@ These changes are ones which we think may surprise users when upgrading to
3737
Clang |release| because of the opportunity they pose for disruption to existing
3838
code bases.
3939

40+
- Fix a bug in reversed argument for templated operators.
41+
This breaks code in C++20 which was previously accepted in C++17.
42+
Clang did not properly diagnose such casese in C++20 before this change. Eg:
43+
44+
.. code-block:: cpp
45+
46+
struct P {};
47+
template<class S> bool operator==(const P&, const S&);
48+
49+
struct A : public P {};
50+
struct B : public P {};
51+
52+
// This equality is now ambiguous in C++20.
53+
bool ambiguous(A a, B b) { return a == b; }
54+
55+
template<class S> bool operator!=(const P&, const S&);
56+
// Ok. Found a matching operator!=.
57+
bool fine(A a, B b) { return a == b; }
58+
59+
To reduce such widespread breakages, as an extension, Clang accepts this code
60+
with an existing warning ``-Wambiguous-reversed-operator`` warning.
61+
Fixes `GH <https://github.com/llvm/llvm-project/issues/53954>`_.
62+
4063
- The CMake variable ``GCC_INSTALL_PREFIX`` (which sets the default
4164
``--gcc-toolchain=``) is deprecated and will be removed. Specify
4265
``--gcc-install-dir=`` or ``--gcc-triple=`` in a `configuration file

clang/lib/Sema/SemaOverload.cpp

Lines changed: 34 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -7723,9 +7723,19 @@ bool Sema::CheckNonDependentConversions(
77237723
++I) {
77247724
QualType ParamType = ParamTypes[I + Offset];
77257725
if (!ParamType->isDependentType()) {
7726-
unsigned ConvIdx = PO == OverloadCandidateParamOrder::Reversed
7727-
? 0
7728-
: (ThisConversions + I);
7726+
unsigned ConvIdx;
7727+
if (PO == OverloadCandidateParamOrder::Reversed) {
7728+
ConvIdx = Args.size() - 1 - I;
7729+
assert(Args.size() + ThisConversions == 2 &&
7730+
"number of args (including 'this') must be exactly 2 for "
7731+
"reversed order");
7732+
// For members, there would be only one arg 'Args[0]' whose ConvIdx
7733+
// would also be 0. 'this' got ConvIdx = 1 previously.
7734+
assert(!HasThisConversion || (ConvIdx == 0 && I == 0));
7735+
} else {
7736+
// For members, 'this' got ConvIdx = 0 previously.
7737+
ConvIdx = ThisConversions + I;
7738+
}
77297739
Conversions[ConvIdx]
77307740
= TryCopyInitialization(*this, Args[I], ParamType,
77317741
SuppressUserConversions,
@@ -10121,11 +10131,23 @@ getImplicitObjectParamType(ASTContext &Context, const FunctionDecl *F) {
1012110131
return M->getFunctionObjectParameterReferenceType();
1012210132
}
1012310133

10124-
static bool haveSameParameterTypes(ASTContext &Context, const FunctionDecl *F1,
10125-
const FunctionDecl *F2) {
10134+
// As a Clang extension, allow ambiguity among F1 and F2 if they represent
10135+
// represent the same entity.
10136+
static bool allowAmbiguity(ASTContext &Context, const FunctionDecl *F1,
10137+
const FunctionDecl *F2) {
1012610138
if (declaresSameEntity(F1, F2))
1012710139
return true;
10128-
10140+
auto PT1 = F1->getPrimaryTemplate();
10141+
auto PT2 = F2->getPrimaryTemplate();
10142+
if (PT1 && PT2) {
10143+
if (declaresSameEntity(PT1, PT2) ||
10144+
declaresSameEntity(PT1->getInstantiatedFromMemberTemplate(),
10145+
PT2->getInstantiatedFromMemberTemplate()))
10146+
return true;
10147+
}
10148+
// TODO: It is not clear whether comparing parameters is necessary (i.e.
10149+
// different functions with same params). Consider removing this (as no test
10150+
// fail w/o it).
1012910151
auto NextParam = [&](const FunctionDecl *F, unsigned &I, bool First) {
1013010152
if (First) {
1013110153
if (std::optional<QualType> T = getImplicitObjectParamType(Context, F))
@@ -10329,14 +10351,14 @@ bool clang::isBetterOverloadCandidate(
1032910351
case ImplicitConversionSequence::Worse:
1033010352
if (Cand1.Function && Cand2.Function &&
1033110353
Cand1.isReversed() != Cand2.isReversed() &&
10332-
haveSameParameterTypes(S.Context, Cand1.Function, Cand2.Function)) {
10354+
allowAmbiguity(S.Context, Cand1.Function, Cand2.Function)) {
1033310355
// Work around large-scale breakage caused by considering reversed
1033410356
// forms of operator== in C++20:
1033510357
//
10336-
// When comparing a function against a reversed function with the same
10337-
// parameter types, if we have a better conversion for one argument and
10338-
// a worse conversion for the other, the implicit conversion sequences
10339-
// are treated as being equally good.
10358+
// When comparing a function against a reversed function, if we have a
10359+
// better conversion for one argument and a worse conversion for the
10360+
// other, the implicit conversion sequences are treated as being equally
10361+
// good.
1034010362
//
1034110363
// This prevents a comparison function from being considered ambiguous
1034210364
// with a reversed form that is written in the same way.
@@ -14526,7 +14548,7 @@ ExprResult Sema::CreateOverloadedBinOp(SourceLocation OpLoc,
1452614548
llvm::SmallVector<FunctionDecl*, 4> AmbiguousWith;
1452714549
for (OverloadCandidate &Cand : CandidateSet) {
1452814550
if (Cand.Viable && Cand.Function && Cand.isReversed() &&
14529-
haveSameParameterTypes(Context, Cand.Function, FnDecl)) {
14551+
allowAmbiguity(Context, Cand.Function, FnDecl)) {
1453014552
for (unsigned ArgIdx = 0; ArgIdx < 2; ++ArgIdx) {
1453114553
if (CompareImplicitConversionSequences(
1453214554
*this, OpLoc, Cand.Conversions[ArgIdx],

clang/test/CXX/over/over.match/over.match.funcs/over.match.oper/p3-2a.cpp

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -324,6 +324,124 @@ bool x = X() == X(); // expected-warning {{ambiguous}}
324324
}
325325
} // namespace P2468R2
326326

327+
namespace GH53954{
328+
namespace friend_template_1 {
329+
struct P {
330+
template <class T>
331+
friend bool operator==(const P&, const T&) { return true; } // expected-note {{candidate}} \
332+
// expected-note {{ambiguous candidate function with reversed arguments}}
333+
};
334+
struct A : public P {};
335+
struct B : public P {};
336+
bool check(A a, B b) { return a == b; } // expected-warning {{use of overloaded operator '==' (with operand types 'A' and 'B') to be ambiguous}}
337+
}
338+
339+
namespace friend_template_2 {
340+
struct P {
341+
template <class T>
342+
friend bool operator==(const T&, const P&) { return true; } // expected-note {{candidate}} \
343+
// expected-note {{ambiguous candidate function with reversed arguments}}
344+
};
345+
struct A : public P {};
346+
struct B : public P {};
347+
bool check(A a, B b) { return a == b; } // expected-warning {{use of overloaded operator '==' (with operand types 'A' and 'B') to be ambiguous}}
348+
}
349+
350+
namespace friend_template_class_template {
351+
template<class S>
352+
struct P {
353+
template <class T>
354+
friend bool operator==(const T&, const P&) { // expected-note 2 {{candidate}}
355+
return true;
356+
}
357+
};
358+
struct A : public P<int> {};
359+
struct B : public P<bool> {};
360+
bool check(A a, B b) { return a == b; } // expected-warning {{ambiguous}}
361+
}
362+
363+
namespace friend_template_fixme {
364+
// FIXME(GH70210): This should not rewrite operator== and definitely not a hard error.
365+
struct P {
366+
template <class T>
367+
friend bool operator==(const T &, const P &) { return true; } // expected-note 2 {{candidate}}
368+
template <class T>
369+
friend bool operator!=(const T &, const P &) { return true; } // expected-note {{candidate}}
370+
};
371+
struct A : public P {};
372+
struct B : public P {};
373+
bool check(A a, B b) { return a != b; } // expected-error{{ambiguous}}
374+
}
375+
376+
namespace member_template_1 {
377+
struct P {
378+
template<class S>
379+
bool operator==(const S &) const; // expected-note {{candidate}} \
380+
// expected-note {{ambiguous candidate function with reversed arguments}}
381+
};
382+
struct A : public P {};
383+
struct B : public P {};
384+
bool check(A a, B b) { return a == b; } // expected-warning {{use of overloaded operator '==' (with operand types 'A' and 'B') to be ambiguous}}
385+
} // namespace member_template
386+
387+
namespace member_template_2{
388+
template <typename T>
389+
class Foo {
390+
public:
391+
template <typename U = T>
392+
bool operator==(const Foo& other) const;
393+
};
394+
bool x = Foo<int>{} == Foo<int>{};
395+
} // namespace template_member_opeqeq
396+
397+
namespace non_member_template_1 {
398+
struct P {};
399+
template<class S>
400+
bool operator==(const P&, const S &); // expected-note {{candidate}} \
401+
// expected-note {{ambiguous candidate function with reversed arguments}}
402+
403+
struct A : public P {};
404+
struct B : public P {};
405+
bool check(A a, B b) { return a == b; } // expected-warning {{use of overloaded operator '==' (with operand types 'A' and 'B') to be ambiguous}}
406+
407+
template<class S> bool operator!=(const P&, const S &);
408+
bool fine(A a, B b) { return a == b; } // Ok. Found a matching operator!=.
409+
} // namespace non_member_template_1
410+
411+
namespace non_member_template_2 {
412+
struct P {};
413+
template<class S>
414+
bool operator==(const S&, const P&); // expected-note {{candidate}} \
415+
// expected-note {{ambiguous candidate function with reversed arguments}}
416+
417+
struct A : public P {};
418+
struct B : public P {};
419+
bool check(A a, B b) { return a == b; } // expected-warning {{use of overloaded operator '==' (with operand types 'A' and 'B') to be ambiguous}}
420+
} // namespace non_member_template_2
421+
422+
namespace class_and_member_template {
423+
template <class T>
424+
struct S {
425+
template <typename OtherT>
426+
bool operator==(const OtherT &rhs); // expected-note {{candidate}} \
427+
// expected-note {{reversed arguments}}
428+
};
429+
struct A : S<int> {};
430+
struct B : S<bool> {};
431+
bool x = A{} == B{}; // expected-warning {{ambiguous}}
432+
} // namespace class_and_member_template
433+
434+
namespace ambiguous_case {
435+
template <class T>
436+
struct Foo {};
437+
template <class T, class U> bool operator==(Foo<U>, Foo<T*>); // expected-note{{candidate}}
438+
template <class T, class U> bool operator==(Foo<T*>, Foo<U>); // expected-note{{candidate}}
439+
440+
void test() {
441+
Foo<int*>() == Foo<int*>(); // expected-error{{ambiguous}}
442+
}
443+
} // namespace ambiguous_case
444+
} // namespace
327445
namespace ADL_GH68901{
328446
namespace test1 {
329447
namespace A {

0 commit comments

Comments
 (0)