1
struct BaseA {
    auto operator==(const BaseA& other) const {return other.a == a;}
    int a;
};

struct B {
    int b;
};

struct A: public BaseA {
    auto operator==(const B& other) const {return other.b == a;}
};

int main() {
    A a{10};
    a == a;

    return 0;
}

It won't compile:

error: no match for ‘operator==’ (operand types are ‘A’ and ‘A’)
note: candidate: ‘auto A::operator==(const B&) const’
note: no known conversion for argument 1 from ‘A’ to ‘const B&’

Doesn't list BaseA::operator== as a candidate.

However, if I comment out A::operator== method, it compiles.

Because of that I thought comparison operators get some special treatment, (sometimes generated for the child class, sometimes not, like those out of rule-of-five), but after a quick search turns out not the case.

Some rules of operator overloading, then?

Adam
  • 1,724
  • 4
  • 21
  • 31

3 Answers3

4

auto operator==(const B& other) const hides the base one, use using

struct A: public BaseA {
    using BaseA::operator==;
    auto operator==(const B& other) const {return other.b == a;}
};

Demo

Jarod42
  • 203,559
  • 14
  • 181
  • 302
  • [A question why hiding happens](https://stackoverflow.com/questions/1628768/why-does-an-overridden-function-in-the-derived-class-hide-other-overloads-of-the) However, the reason given (from Stroustrup's book) doesn't apply to this case, as my methods are const. – Adam May 05 '20 at 14:14
  • Unsure of the reason you talk about... I don't see `const` concern in link... your method has same name than base class's one, so hiding (or override in virtual case) happens. – Jarod42 May 05 '20 at 14:27
  • I talk about the mentioned "slicing" in the answer. – Adam May 05 '20 at 14:46
  • There are examples with slicing and promotion (and consistency with namespace scope) for the rational. Still unsure the purpose of the comment, but hiding only "problematic" overloads would be IMO less intuitive. Not even sure that "problematic" would be well defined there... – Jarod42 May 05 '20 at 15:08
  • I managed to do it without `using`, please check my new answer, if the explanation I give is correct or if it has potential downsides. – Adam May 06 '20 at 20:29
2

Nothing special about operators here, you'd get the similar error with:

struct BaseA {
    auto foo(const BaseA& other) const {return other.a == a;}
    int a;
};

struct B {
    int b;
};

struct A: public BaseA {
    auto foo(const B& other) const {return other.b == a;}
};

int main() {
    A a{10};
    a.foo(a);
}

Compiler finds a foo in A and stops there. If you want to have both you need to explicitly pull it in scope:

struct A: public BaseA {
    auto foo(const B& other) const {return other.b == a;}
    using BaseA::foo; 
};
463035818_is_not_an_ai
  • 109,796
  • 11
  • 89
  • 185
0

I found a fix, without writing out using for each comparison operator I add in the child class:

Make the operator non-member and pull it out to the enclosing scope of A (in this example the global scope):

auto operator==(const A&, const B&)

If you want to access private members of A or the protected of its base class, you can make the operator a friend of A. (Same for B.)

Why it works

The hiding (meaning that if the name is found in a scope, it won't look in next ones, those the lookup algorithm is going to consider in the next step) won't stop this from working because for operators, two separate lookups, subsequently unioned, are performed:

For an operator used in expression (e.g., operator+ used in a+b) two separate lookups are performed: for the non-member operator overloads and for the member operator overloads (for the operators where both forms are permitted). Those sets are then merged with the built-in operator overloads on equal grounds.

-- cppreference

Thus the non-member operator== in global namespace won't prevent searching for BaseA::operator==, which is searched for in the member operator lookup, starting in A (but there's no operator== now), so moving on the the next scope, the base class BaseA.


This way of fixing it is important for me, because I use a strong types library and I just wanted to add just one operator for the strong type, wrapping an integer. I'm using the library ti avoid writing out the boilerplate myself, and yet every time I would add an operator, I would have to use the one in the library.

Adam
  • 1,724
  • 4
  • 21
  • 31