Skip to content

Commit 47747da

Browse files
authored
[clang] Handle templated operators with reversed arguments (llvm#69595)
llvm#68999 correctly computed conversion sequence for reversed args to a template operators. 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 llvm#53954
1 parent ba8565f commit 47747da

File tree

3 files changed

+100
-10
lines changed

3 files changed

+100
-10
lines changed

clang/docs/ReleaseNotes.rst

+21
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,27 @@ 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. Eg:
42+
43+
.. code-block:: cpp
44+
45+
struct P {};
46+
template<class S> bool operator==(const P&, const S&);
47+
48+
struct A : public P {};
49+
struct B : public P {};
50+
51+
// This equality is now ambiguous in C++20.
52+
bool ambiguous(A a, B b) { return a == b; }
53+
54+
template<class S> bool operator!=(const P&, const S&);
55+
// Ok. Found a matching operator!=.
56+
bool fine(A a, B b) { return a == b; }
57+
58+
To reduce such widespread breakages, as an extension, Clang accepts this code
59+
with an existing warning ``-Wambiguous-reversed-operator`` warning.
60+
Fixes `GH <https://github.com/llvm/llvm-project/issues/53954>`_.
4061

4162
C/C++ Language Potentially Breaking Changes
4263
-------------------------------------------

clang/lib/Sema/SemaOverload.cpp

+18-10
Original file line numberDiff line numberDiff line change
@@ -7688,7 +7688,7 @@ bool Sema::CheckNonDependentConversions(
76887688
QualType ParamType = ParamTypes[I + Offset];
76897689
if (!ParamType->isDependentType()) {
76907690
unsigned ConvIdx = PO == OverloadCandidateParamOrder::Reversed
7691-
? 0
7691+
? Args.size() - 1 - (ThisConversions + I)
76927692
: (ThisConversions + I);
76937693
Conversions[ConvIdx]
76947694
= TryCopyInitialization(*this, Args[I], ParamType,
@@ -10085,11 +10085,19 @@ getImplicitObjectParamType(ASTContext &Context, const FunctionDecl *F) {
1008510085
return M->getFunctionObjectParameterReferenceType();
1008610086
}
1008710087

10088-
static bool haveSameParameterTypes(ASTContext &Context, const FunctionDecl *F1,
10089-
const FunctionDecl *F2) {
10088+
// As a Clang extension, allow ambiguity among F1 and F2 if they represent
10089+
// represent the same entity.
10090+
static bool allowAmbiguity(ASTContext &Context, const FunctionDecl *F1,
10091+
const FunctionDecl *F2) {
1009010092
if (declaresSameEntity(F1, F2))
1009110093
return true;
10092-
10094+
if (F1->isTemplateInstantiation() && F2->isTemplateInstantiation() &&
10095+
declaresSameEntity(F1->getPrimaryTemplate(), F2->getPrimaryTemplate())) {
10096+
return true;
10097+
}
10098+
// TODO: It is not clear whether comparing parameters is necessary (i.e.
10099+
// different functions with same params). Consider removing this (as no test
10100+
// fail w/o it).
1009310101
auto NextParam = [&](const FunctionDecl *F, unsigned &I, bool First) {
1009410102
if (First) {
1009510103
if (std::optional<QualType> T = getImplicitObjectParamType(Context, F))
@@ -10274,14 +10282,14 @@ bool clang::isBetterOverloadCandidate(
1027410282
case ImplicitConversionSequence::Worse:
1027510283
if (Cand1.Function && Cand2.Function &&
1027610284
Cand1.isReversed() != Cand2.isReversed() &&
10277-
haveSameParameterTypes(S.Context, Cand1.Function, Cand2.Function)) {
10285+
allowAmbiguity(S.Context, Cand1.Function, Cand2.Function)) {
1027810286
// Work around large-scale breakage caused by considering reversed
1027910287
// forms of operator== in C++20:
1028010288
//
10281-
// When comparing a function against a reversed function with the same
10282-
// parameter types, if we have a better conversion for one argument and
10283-
// a worse conversion for the other, the implicit conversion sequences
10284-
// are treated as being equally good.
10289+
// When comparing a function against a reversed function, if we have a
10290+
// better conversion for one argument and a worse conversion for the
10291+
// other, the implicit conversion sequences are treated as being equally
10292+
// good.
1028510293
//
1028610294
// This prevents a comparison function from being considered ambiguous
1028710295
// with a reversed form that is written in the same way.
@@ -14421,7 +14429,7 @@ ExprResult Sema::CreateOverloadedBinOp(SourceLocation OpLoc,
1442114429
llvm::SmallVector<FunctionDecl*, 4> AmbiguousWith;
1442214430
for (OverloadCandidate &Cand : CandidateSet) {
1442314431
if (Cand.Viable && Cand.Function && Cand.isReversed() &&
14424-
haveSameParameterTypes(Context, Cand.Function, FnDecl)) {
14432+
allowAmbiguity(Context, Cand.Function, FnDecl)) {
1442514433
for (unsigned ArgIdx = 0; ArgIdx < 2; ++ArgIdx) {
1442614434
if (CompareImplicitConversionSequences(
1442714435
*this, OpLoc, Cand.Conversions[ArgIdx],

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

+61
Original file line numberDiff line numberDiff line change
@@ -324,6 +324,67 @@ 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&); // 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&); // 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 member_template {
351+
struct P {
352+
template<class S>
353+
bool operator==(const S &) const; // expected-note {{candidate}} \
354+
// expected-note {{ambiguous candidate function with reversed arguments}}
355+
};
356+
struct A : public P {};
357+
struct B : public P {};
358+
bool check(A a, B b) { return a == b; } // expected-warning {{use of overloaded operator '==' (with operand types 'A' and 'B') to be ambiguous}}
359+
}
360+
361+
namespace non_member_template_1 {
362+
struct P {};
363+
template<class S>
364+
bool operator==(const P&, const S &); // expected-note {{candidate}} \
365+
// expected-note {{ambiguous candidate function with reversed arguments}}
366+
367+
struct A : public P {};
368+
struct B : public P {};
369+
bool check(A a, B b) { return a == b; } // expected-warning {{use of overloaded operator '==' (with operand types 'A' and 'B') to be ambiguous}}
370+
371+
template<class S>
372+
bool operator!=(const P&, const S &);
373+
bool fine(A a, B b) { return a == b; } // Ok. Found a matching operator!=.
374+
}
375+
}
376+
377+
namespace non_member_template_2 {
378+
struct P {};
379+
template<class S>
380+
bool operator==(const S&, const P&); // expected-note {{candidate}} \
381+
// expected-note {{ambiguous candidate function with reversed arguments}}
382+
383+
struct A : public P {};
384+
struct B : public P {};
385+
bool check(A a, B b) { return a == b; } // expected-warning {{use of overloaded operator '==' (with operand types 'A' and 'B') to be ambiguous}}
386+
}
387+
327388
#else // NO_ERRORS
328389

329390
namespace problem_cases {

0 commit comments

Comments
 (0)