46

Consider this valid C++17 example:

struct A {
   bool operator==(const A&);
};


int main() {
   return A{} == A{};
}

When compiled in clang with -std=c++20 it gives:

<source>:7:15: warning: ISO C++20 considers use of overloaded operator '==' (with operand types 'A' and 'A') to be ambiguous despite there being a unique best viable function [-Wambiguous-reversed-operator]

   return A{} == A{};

          ~~~ ^  ~~~

<source>:2:9: note: ambiguity is between a regular call to this operator and a call with the argument order reversed

   bool operator==(const A&);

Does this warning mean that C++20 disallows using a typical comparison operator to compare two objects of the same type? What is the correct alternative? Is the situation expected to change in future drafts?

Lack
  • 1,625
  • 1
  • 17
  • 29
  • 2
    I will leave the details to a language lawyer, but making the function `const` should let the warning evaporate. I think the warning exists, since you invoke `operator==` on a temporary. – aep Feb 25 '20 at 02:45
  • 3
    @aep It's true the warning goes away by adding `const`, but not by removing the temporary (`A a, b; a == b`). Thanks, I guess it must be related to one side of the `==` being const but not the other. – Lack Feb 25 '20 at 03:03
  • There aren’t any more changes for C++20 (except for those still being merged from the last meeting for that version). – Davis Herring Feb 25 '20 at 03:13
  • 1
    I swear, half the reason I prefer to follow the ["use non-member overloads" guideline](https://stackoverflow.com/a/4421719/364696) is it's so easy to forget to declare the member function itself `const`, where the non-member function just needs to remember to declare the arguments `const`. – ShadowRanger Feb 25 '20 at 03:25

2 Answers2

50

Does this warning mean that C++20 disallows using a typical comparison operator to compare two objects of the same type? What is the correct alternative? Is the situation expected to change in future drafts?

This isn't really a typical comparison operator, it's already kind of wrong - since it only allows a const object on one side (your type A wouldn't satisfy the new equality_comparable concept either, even without any langauge changes).

You have to write it this way:

struct A {
   bool operator==(const A&) const;
//                          ^^^^^^
};

This is the final rule for C++20.


The specific issue is that in C++20, comparison operators add a new notion of rewritten and reversed candidates. So lookup for the expression a == b will also end up matching operators like b == a. In the typical case, this means you have to write fewer operators, since we know equality is commutative.

But if you have a const-mismatch, what happens is you end up with these two candidates:

bool operator==(/* this*/ A&, A const&); // member function
bool operator==(A const&, /* this*/ A&); // reversed member function

With two arguments of type A. The first candidate is better in the first argument, and the second candidate is better in the second argument. Neither candidate is better than the other, hence ambiguous.

Barry
  • 286,269
  • 29
  • 621
  • 977
5

It is a general rule of overload resolution that each argument type must be separately at least as close to the parameter type for a selected function as to the parameter type for any other:

struct A {A(int);};
void f(long,int);   // #1
void f(int,A);      // #2
void g() {f(0,0);}  // error: ambiguous

The much worse conversion for the second argument for #2 doesn’t make up for the intlong conversion on the first argument.

In C++20, various rewrite rules have been added to obviate the need to write so many all-but-identical comparison operator overloads. While the trivial ambiguities between hand-written “reversed candidates” and identical compiler-generated ones are handled by tie-breaker rules that prefer real functions, that’s (again) not enough to make up for a worse conversion for any argument.

Comparison operators written carefully according to accepted (C++17) practices will very rarely run afoul of this, but questionable signatures like this (with asymmetric const) may very well be problematic (in new ways). Hopefully more bugs are found than are caused by this extension.

Davis Herring
  • 36,443
  • 4
  • 48
  • 76
  • 3
    @Barry: Well, (Clang) downgrading it to a warning gives people another way to convert “slowly” (`-Wno-error=ambiguous-reversed-operator` in addition to some sort of forward-looking C++17 mode). – Davis Herring Feb 25 '20 at 03:52